如果你在网上阅读,那么有很多人声称,如果你使用前向声明,那么C++它会节省你的编译时间。通常的理论是,如果我使用前向声明,#include
意味着仅仅是文本替换,那么我的编译器不需要解析标头并可能编译它,因此可以节省时间。我发现这种说法很难相信,因为考虑到我通常看到这样的代码:
// B.h
class A;
class B {
public:
void doSomething(A& a);
}
在这种情况下,是的,我们不需要在转发声明时将A.h
包含在B.h
中,但问题是,最终B.cpp
,我们需要一个完整的类型A
来使用它的方法和数据成员。所以我发现在几乎所有情况下,我们都需要在B.cpp
中包含A.h
。
那么前向声明如何实际节省编译时间呢?我看到有人用基准来证明,如果他们使用前向声明而不是#include
s,编译时间实际上会下降,所以这里一定有什么我不明白的地方......
我知道节省编译时间不是前向声明的唯一目的,我知道它还有其他目的。我只是想了解为什么有些人声称它可以节省编译时间。
编译时间
问题在于,最终
B.cpp
,我们需要一个完整的类型A
来使用它的方法和数据成员。
是的,这是一个典型的模式。前向声明一个类(例如A
) 在标头中(例如B.h
),然后在对应于该标头 (B.cpp
) 的源代码中,包括前向声明类 (A.h
) 的标头。
所以我发现在几乎所有情况下,我们都需要在
B.cpp
中包含B.h
。
正确的前向声明在编译相应的源代码时不会节省时间。编译其他使用B
的源代码时,可以节省成本。例如:
其他.cpp
#include "B.h"
// Do stuff with `B` objects.
// Make no use of `A` objects.
假设此文件不需要A.h
中的定义。这就是节省的地方。在编译other.cpp
时,如果B.h
使用A
的前向声明,则不需要处理A.h
。也不需要处理A.h
本身包含的标头,等等。现在将此效果乘以包含B.h
的文件数,直接或间接地。
请注意,这里有复合效应。"A.h
本身包含的标头"和"包含B.h
的文件"的数量将是用前向声明替换任何#include
语句之前的数字。(一旦你开始进行这些替换,数字就会下降。
效果有多大?不像以前那么多了。尽管如此,只要我们在理论上谈论,即使是最小的储蓄仍然是储蓄。
重建时间
我认为比原始编译时间(构建所有内容)更关注的是重建时间。也就是说,仅编译受所做更改影响的文件所需的时间。
假设有十个文件依赖于B.h
但不依赖于A.h
。如果B.h
包含A.h
,那么这十个文件将受到A.h
更改的影响。如果B.h
转发声明A
,那么这些文件不会受到A.h
更改的影响,从而减少了在这些更改后重建的时间。
现在假设有另一个类,称之为B2
,它也可以选择转发声明A
而不是包含标头。也许还有另外十个文件依赖于B2
但不依赖于B
,也不依赖于A
。现在有二十个文件在更改A
后不需要重新编译。
但为什么要止步于此呢?让我们将B3
到B10
添加到组合中。现在有一百个文件在更改A
后不需要重新编译。
添加另一个图层。假设有一个C.h
可以选择转发声明B
而不是包含B.h
。通过使用前向声明,对A.h
的更改不再需要重新编译使用C.h
的十个文件。而且,当然,我们假设每个B
到B10
都有十个这样的文件。现在,我们最多10*10*10
个文件,这些文件在A.h
更改时不需要重新编译。
带走
这是一个简化的示例,用作演示。关键是存在由#include
行创建的依赖树森林。(此类树的根将是感兴趣的头文件,其子级是#include
它的文件。其中一个树中的每个叶子表示一个文件,当感兴趣的头文件中发生更改时,必须编译该文件。树中的叶子数量随着深度呈指数增长,因此移除树枝(通过用前向声明替换#include
)会对重建时间产生巨大影响。或者也许可以忽略不计的影响。这是理论,不是实践。
我应该注意,与这个问题一样,这个答案侧重于编译时间,而不是要考虑的其他因素。这不应该是关于前向声明的利弊的全面指南,只是对它们如何节省编译时间的解释。