在多个类彼此相互引用的场景下,为了加快编译速度、降低头文件之间的依赖耦合,通常会采用前置声明(forward declaration)的方式来解决。例如下面这段代码:
#pragma once
class A;
class Test
{
A *pimpl;// OK ,指针,大小已固定,没问题
};
这样一来,Test 的头文件就不必再去 #include "A.h",对 A 的实际操作可以推迟到 Test 的 cpp 文件中再引入,头文件的依赖就清爽多了。
不过,前置声明并不是万能的,下面这段代码就会出问题:
#pragma once
class A;
class Test
{
A aa;// ERROR , A的大小是? 包含哪些操作?
};
原因在于,只有一个前置声明时,编译器并不知道类 A 的完整定义——既不知道它占多大内存,也不知道它提供了哪些成员函数。所以在 Test 类里以值的形式持有 A,或者直接调用 A 的成员,都会因为 类型不完整(incomplete type)而编译失败。
pImpl 手法
为了稳妥地解决这个问题,C++ 里常用的做法就是 pImpl(pointer to implementation),其思路和前面第一段代码是一致的:头文件里只保留一个指向实现类的指针,完整的实现类定义完全藏在 cpp 文件中。这样对外头文件就只依赖一个前置声明,实现细节的变化也不会触发使用方的重新编译。
相关参考
- Bridge(桥接)模式:同样是通过持有一个抽象实现指针,把接口和实现解耦开来,思路与 pImpl 高度相似。