标题包含C++中的蠕变
Header Include Creep in C++
当我开始学习C++时,我了解到头文件通常应该包含在其他头文件中。刚才有人告诉我,我应该在.cpp文件中包含一个特定的头文件,以避免头包含蠕变。
有人能告诉我这到底是什么,为什么会有问题吗?也许可以给我一些文档,告诉我什么时候我想在另一个头中包含一个文件,什么时候我应该在.cpp文件中包括一个文件?
"蠕变"是指包含一个标头,包括许多其他标头。这会带来一些不必要的后果:
- 倾向于每个源文件都间接地包含每个标头,因此对任何标头的更改都需要重新编译所有内容
- 循环依赖导致心痛的可能性更大
通过只声明所需的类,而不是包含给出完整定义的头,通常可以避免从另一个头中包含一个头。这样的不完全类型可以以各种方式使用:
// Don't need this
//#include "thingy.h"
// just this
class thingy;
class whatsit {
// You can declare pointers and references
thingy * p;
thingy & r;
// You can declare static member variables (and external globals)
// They will need the header in the source file that defines them
static thingy s;
// You can declare (but not define) functions with incomplete
// parameter or return types
thingy f(thingy);
};
有些事情确实需要完整的定义:
// Do need this
#include "thingy.h"
// Needed for inheritance
class whatsit : thingy {
// Needed for non-static member variables
thingy t;
// Needed to do many things like copying, accessing members, etc
thingy f() {return t;}
};
有人能告诉我到底是什么吗
这不是一个编程术语,但在英语上下文中解释它意味着它是不必要的#include
语句的引入。
为什么有问题
因为编译代码需要时间。所以编译不必要的代码需要不必要的时间。
也许可以给我一些文档,告诉我什么时候我想在另一个头中包含一个文件,什么时候我应该在.cpp文件中包括一个文件?
如果您的标头需要类型的定义,则需要包含定义该类型的标头。
#include "type.h"
struct TypeHolder
{
Type t;
// ^^^^ this value type requires the definition to know its size.
}
如果您的标头只需要类型的声明,则该定义是不必要的,include也是不必要的。
class Type;
struct TypeHolder
{
Type * t;
// ^^^^^^ this pointer type has the already-known size of a pointer,
// so the definition is not required.
}
作为一则支持这种做法价值的轶事,我曾经参与过一个项目,该项目的代码库需要一个小时才能完全编译。而更改一个头通常会花费下一次编译的大部分或全部时间
在适用的情况下,将前向声明添加到标头中,而不是立即包含,将完整编译时间减少到16分钟,并且更改标头通常不需要完全重建
嗯,他们可能指的是一些东西("包括蠕变"不是我以前听过的术语)。如果作为极端规则,仅包含来自标头的标头(除了每个源文件一个匹配的标头):
- 编译时间会增加,因为必须为所有源文件编译许多标头
- 不必要的依赖性增加,例如,在基于依赖性的构建系统中,修改一个标头可能会导致不必要的许多其他文件的重新编译,从而增加编译时间
- 命名空间污染的可能性增加
- 循环依赖关系成为一个问题(两个标头需要相互包含)
- 其他更高级的依赖关系相关主题(例如,这违背了不透明指针的目的,后者在某些类型的应用程序中会导致许多设计问题)
根据一般经验,包含一个标头中仅编译该标头中的代码所需的最少数量(即,如果标头本身包含,则足以允许其进行编译,但不超过此数量)。这将解决上述所有问题。
例如,如果代码中有一个使用std::list
的类的源文件,但该类本身没有使用std::list
的成员或函数参数,则没有理由从该类的标头中使用#include <list>
,因为该类标头中没有任何内容使用std::list
。从您实际需要的源文件中执行,而不是在标头中执行,在标头中,使用类的所有内容都必须编译<list>
。
另一种常见的技术是正向声明指针类型。这是解决循环依赖问题的唯一真正方法,并且具有上面列出的一些编译时间优势。例如,如果两个类相互引用,但仅通过指针:
// A.h
class B; // forward declaration instead of #include "B.h"
class A {
B *b;
}
// B.h
class A; // forward declaration instead of #include "A.h"
class B {
A *a;
}
然后在源文件中包含每个文件的头,其中实际需要定义。
您的头文件应包括单独(或首先)包含在源文件中时需要编译的所有头文件。在许多情况下,您可能允许通过使用前向声明来编译标头,但在包含您的标头之前,您不应该依赖于包含特定标头的人。
有很多时间需要考虑,但也有依赖性。如果A.h包含B.h和viceversa,代码将不会编译。你可以通过向前引用你的类,然后在cpp-中包括标题来解决这个问题
A.h
class B;
class A
{
public:
void CreateNewB():
private:
B* m_b;
};
A.cpp
#include "B.h"
A::CreateNewB()
{
m_b = new B;
}
- 如何导出包含具有"std::unique_ptr"值的"std::map"属性的
- 从包含m行的文件中提取n行,必要时(惰性地)重复该文件
- 编译包含字符串的代码时遇到问题
- c++库的公共头文件中应该包含什么
- 将包含C样式数组的对象初始化为成员变量(C++)
- 是否需要删除包含对象的"pair"?
- 函数何时会在c++中包含stack_Unwind_Resume调用
- 如何将包含epoch时间的十六进制字符串转换为time_t
- 使用mongocxx驱动程序时包含头文件问题
- 如何在h文件中包含.o对象文件
- 在混合代码库中将C转换为C++时出现许多包含错误
- VS2017,C++包含目录与附加包含目录,子文件夹包含失败-但为什么
- cmath抛出错误C2062、C2059、C2143和C2447.cmath包含在矢量文件中
- 为什么您需要C++头文件的包含保护
- 无法在UE4中包含BP类到CPP类
- g++ 说函数不存在,即使包含正确的标头
- 在C++代码中包含opencv时,使用ctypes创建.so文件
- Visual C++GC接口如何启用它以及要包含哪个库
- 当调用switch语句中的函数时(即使函数不包含循环),似乎是永不结束的循环的问题
- 为什么包含windows.h会产生语法错误,从而阻止类的实例化?(C2146,C2065)