cpp前向声明
当我们使用cpp
设计数据结构时,常常出现两个类互相依赖的情况,如下,假设我们有一个test.h文件,里面包含两个类的定义:
// A的成员变量包含B对象,B的成员变量包含A对象
class A {
B b_;
};
class B {
A a_;
};
前向声明
首先,我们会碰到一个问题,在class A
定义时,class B
还没有被定义,那么编译器会报错找不到class B
,那么这时候前向声明就派上用场了,我们可以在class A
定义之前给出class B
的前向声明,像下面这样,这样编译器就能明白存在class B
这样一个类:
// class B的前向声明
class B;
class A {
B b_;
};
class B {
A a_;
};
但是这样我们的代码还是不能编译通过。因为前向声明仅仅给了编译器一个提示,存在class B
这样一个类,但是并没有这个类的完整定义。那么当编译器想要定义A类的对象时,发现需要B类的完整对象定义,再去找B类的完整定义时,发现又需要A类的完整定义,出现相互依赖的情况。而且在互相依赖的情况,因为两个类都包含了对方的完整对象,这种递归让编译器无法计算出两个类的大小,因此需要将对象类对象声明为一个指针,指针的大小是固定的,因此编译器能够顺利计算出两个类的大小,并顺利通过编译,类似下面这样
class B;
class A {
B *b_;
// 智能指针也是可以的,std::unique_ptr<B> b_;
}
class B {
A *a_;
}
继续深入
由于类在定义时支持定义不完整类的指针(例如:前向声明),这样我们的代码就能通过编译了。
但是当我们继续深入,想要在A类中调用B类的成员变量或者方法时,就又会出现问题,类似下面这种情况:
class B;
class A {
B *b_;
// 智能指针也是可以的,std::unique_ptr<B> b_;
void write_A() {
b_->write_B();
}
}
class B {
A *a_;
void write_B() {
// implement
}
}
这种情况是因为从class A
的视角来看,现在它只拥有class B
的前向声明,而前向声明只是一种类的不完整定义,claas A
并不知道class B
包含哪些成员变量或者方法,因此这种情况下编译器会报错:不能调用不完整类型B的方法。遇到这种情况就只能让class A事先知道有write_B()
这个方法的存在,最好的解决办法就是,再额外创建一个.cpp
文件包含实现,并包含class A
和class B
的头文件定义,而在头文件中则只保留前向声明,类似下面这样:
// test.h文件
class A;
class B;
class A {
B *b_;
void write_A();
}
class B {
A *a_;
void write_B();
}
// test.cpp文件
#include "test.h"
A:write_A() {
b_->write_B();
}
B:write_B() {
// implement
}
总结
这样我们就完美的解决了两个类之间互相依赖的问题,不过这种方式必须引入一个cpp
文件的存在,按我的习惯,我很喜欢把实现都放在.h
文件里,因为这样我只需要包含这个头文件就能直接用这个类,而不用链接,比较方便。不过这种方式只适合简单的类定义,复杂的类还是要规范地将定义和实现分开。