绝对地。C/C++ 构建模型是……咳咳……不合时宜(最好说)。对于大型项目,它变成了一个严肃的 PITA。
正如 Neil 正确指出的那样,这不应该是你的类设计的默认方法,除非你真的需要,否则不要走自己的路。
打破循环包含引用是您必须使用前向声明的原因之一。
// a.h
#include "b.h"
struct A { B * a; }
// b.h
#include "a.h" // circlular include reference
struct B { A * a; }
// Solution: break circular reference by forward delcaration of B or A
减少重建时间——想象一下下面的代码
// foo.h
#include <qslider>
class Foo
{
QSlider * someSlider;
}
现在每个直接或间接拉入 Foo.h 的 .cpp 文件也拉入 QSlider.h 及其所有依赖项。那可能是数百个 .cpp 文件!(预编译的头文件有点帮助 - 有时很有帮助 - 但它们将磁盘/CPU压力转变为内存/磁盘压力,因此很快就会达到“下一个”限制)
如果头文件只需要一个引用声明,这种依赖通常可以限制在几个文件中,例如 foo.cpp。
减少增量构建时间- 在处理您自己的(而不是稳定的库)头文件时,效果更加明显。想象一下你有
// bar.h
#include "foo.h"
class Bar
{
Foo * kungFoo;
// ...
}
现在,如果您的大部分 .cpp 需要拉入 bar.h,它们也会间接拉入 foo.h。因此, foo.h 的每次更改都会触发所有这些 .cpp 文件的构建(甚至可能不需要知道 Foo!)。如果 bar.h 使用 Foo 的前向声明,则对 foo.h 的依赖仅限于 bar.cpp:
// bar.h
class Foo;
class Bar
{
Foo * kungFoo;
// ...
}
// bar.cpp
#include "bar.h"
#include "foo.h"
// ...
它是如此普遍,以至于它是一种模式——PIMPL模式。它的用途有两个:首先它提供真正的接口/实现隔离,另一个是减少构建依赖关系。在实践中,我会以 50:50 衡量它们的有用性。
您需要在标头中引用,不能直接实例化依赖类型。这限制了可以应用前向声明的情况。如果您明确地这样做,通常会为此使用实用程序类(例如boost::scoped_ptr)。
构建时间值得吗? 绝对,我会说。在最坏的情况下,构建时间随着项目中文件的数量呈多项式增长。其他技术——比如更快的机器和并行构建——只能提供百分比增益。
构建速度越快,开发人员测试他们所做的事情的频率越高,单元测试运行的频率越高,构建中断的修复速度就越快,开发人员最终拖延的情况就越少。
在实践中,管理构建时间虽然对大型项目(例如,数百个源文件)至关重要,但它仍然对小型项目产生“舒适差异”。此外,事后添加改进通常是一种耐心练习,因为单个修复可能只会缩短 40 分钟构建的几秒钟(或更少)。