Include Guards:为什么C ++编译器不会自动包含每个头文件一次?

时间:2014-10-26 04:16:51

标签: c++ header-files

使用头文件时,每个头文件只应包含一次 例如,假设我有三个班级。 class Aclass Bclass C

class A在文件Ah中声明,class B在文件Bh中声明,class C在文件Ch中声明,并且它们在各自的{{1}中定义文件   .cpp

A.cpp

#include "A.h" class A { } 文件中,以下是该类的定义。

B.cpp

同样适用于#include "A.h" #include "B.h" class B { A a; } 文件。

C.cpp

现在,如果未在头文件中写入包含保护,那么g ++编译器将抛出错误 我的问题是,为什么我们需要指定包含警卫?每个头文件只应包含一次并不常见吗?为什么编译器本身不处理多个包含?

3 个答案:

答案 0 :(得分:3)

如上所述,有时您确实需要多次调用包含文件;并且可能有许多情况需要这样做。

这有用的一个例子是优化大型复杂模板的实例化。考虑一些典型的大型复杂模板类

template<typename T> class ComplicatedTemplate {

// ... Boring stuff goes here

};

在每个翻译单元中实例化和编译这个大型模板,对于相同的模板类型,一遍又一遍地变得非常古老。它减慢了编译速度,并且不必要地使每个对象模块膨胀,只是让链接器处理剥离大量重复的模板实例化。这是很多浪费的工作。

许多编译器都提供了控制模板实例化的方法。具体细节可能会有所不同,但我会使用gcc使用的典型方法,您可以在这里阅读:

https://gcc.gnu.org/onlinedocs/gcc/Template-Instantiation.html

说,你想要在某些翻译单元{&#34; complex.cpp&#34; ,并在头文件中将它们声明为extern。

好的,所以你最终会在ComplicatedTemplate<std::vector<int>>

中结束
ComplicatedTemplate<std::vector<char>>

然后,在ComplicatedTemplate<std::string<std::string>>

complicated_template.H

好的,除了一个不便之外,这样才能正常工作。如果您还决定将template<typename T> class ComplicatedTemplate { // ... Boring stuff goes here }; extern template ComplicatedTemplate<std::vector<int>>; extern template ComplicatedTemplate<std::vector<char>>; extern template ComplicatedTemplate<std::vector<std::string>>; 或其他任何内容添加到预先实例化的模板列表中,则需要在两个位置完成;在头文件和complicated.cpp

这是一种消除这种重复的典型方法:

#include "complicated_template.H" template ComplicatedTemplate<std::vector<int>>; template ComplicatedTemplate<std::vector<char>>; template ComplicatedTemplate<std::vector<std::string>>;

ComplicatedTemplate<std::vector<SomeCustomType>>

complicated.cpp

complicated_template.H

然后,在template<typename T> class ComplicatedTemplate { // ... Boring stuff goes here }; #include "complicated_template_inst.H"

complicated_template_inst.H

现在,预先实例化的模板实例列表位于一个位置。使用前面的示例,添加:

#ifndef EXTERN
#define EXTERN
#endif

EXTERN template ComplicatedTemplate<std::vector<int>>;
EXTERN template ComplicatedTemplate<std::vector<char>>;
EXTERN template ComplicatedTemplate<std::vector<std::string>>;

具有防止在每个需要该模板实例的翻译单元中浪费实例化此模板,以及在complicated.cpp翻译单元中明确实例化该模板的效果。

您将在许多大型C ++库中看到这种方法。他们通常会定义他们的模板,然后通过拉入一个包含一些preprocessor-fu的单独的#include文件来预先实例化它们。在拉入外部可见的头文件后,实际的共享库还将第二次包含第二个文件,预处理器相应地被操作以将这些外部模板声明转换为模板实例化。

答案 1 :(得分:3)

  

我的问题是,为什么我们需要指定包含警卫?每个头文件只应包含一次并不常见吗?为什么编译器本身不处理多个包含?

因为并非所有标头都是如此。可以多次包含并且能够执行此操作的标头的一个示例实际上是<assert>标头。


尝试修复复制和粘贴文件内容的标题系统并没有任何意义。真的,我们只需要转到better build model

答案 2 :(得分:0)

我们通常将编译视为一个步骤,但实际上涉及到各种步骤,对于C ++,其中一个阶段是预处理,它处理{{1} } stuff,它基本上将每个头文件的内容(以及#include <header.h>之类的内容)放在主文件中,因此,如果你没有做出适当的条件,你的主源文件最终会重复代码。< / p>

例如,假设您有两个文件:

#define

// a.h
class A {

};

在实际编译之前// b.cpp #include "a.h" #include "a.h" int main() { return 0; } 将由预处理器处理,使用b.cpp,结果是这样的:

g++

最后一个代码就是编译器的工作原理。此时编译器无法帮助太多。

这是一个非常简单的例子,但我认为它可以帮到你。