C++ 03 - 前置声明
前置声明
定义
所谓前置声明(forward declaration)是类, 函数和模板的纯粹声明, 没伴随着其定义.
使用场景
头文件A包含另一个头文件B, 是为了引入在头文件A中使用的类, 函数, 结构体, 枚举或其他实体的声明. 一般来说, 只有在自己的类中将某个类的对象作为数据成员使用时, 或者需要继承某个类时, 才应该包含那个类的头文件. 传统上来说, 前置声明可以在下列情况下使用:
不需要知道类的大小. 如果包含的类要作为成员变量或打算从包含类派生子类, 那么编译器需要知道类的大小.
没有引用类的任何成员方法. 引用类的成员方法需要知道方法原型, 即参数和返回值类型.
没有引用类的任何成员变量. 不过, 本身就不应该把类的成员变量暴露.
比如:
1
2
3
4
5
6
7
8class B;
class A {
public:
void SetObject(const B& obj);
private:
B* m_obj;
};如果要修改A的定义使得编译器知道类B的实际大小, 那么必须包含类B实际的声明, 即必须包含B的头文件:
1
2
3
4
5
6
7
8
class A {
public:
void SetObject(const B& obj);
private:
B m_obj;
}
优点
前置声明能够节省编译时间, 多余的
#include
会迫使编译器展开更多的文件, 处理更多的输入.前置声明能够节省不必要的重新编译时间.
#include
使代码因为头文件中无关的改动而被重新编译多次.
缺点
前置声明隐藏关系, 头文件改动时, 用户代码会跳过必要的重新编译过程.
前置声明可能会被库的后续更改所破坏. 前置声明函数或模板有时会妨碍头文件变动其API. 例如扩大参数类型, 加上自带默认参数的模板形参等.
前置声明来自命名空间
std::
的symbol时, 其行为未定义.前置声明可能会破坏逻辑:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21// b.h
struct B {};
struct D : B {};
// good_user.cc
// struct B;
// struct D;
void f(B*) {
}
void f(void*) {
}
int main() {
f(nullptr);
}如果是使用前置声明代替
#include
, 那么实际上会调用f(void*), 因为前置声明会隐藏类的依赖关系.前置声明了不少来自头文件的symbol时, 就会比单单一行的
include
冗长.仅仅为了能前置声明而重构代码(比如使用指针成员代替对象成员)会使代码变得更慢更复杂.
delete一个不完整类型的指针时, 如果这个类型有non-trival的析构函数, 那么这种行为是未定义的.
结论
尽量避免前置声明那些定义在其他项目中的实体.
函数: 总是使用
#include
.类模板: 优先使用
#include
.