使用头文件时,每个头文件只应包含一次
例如,假设我有三个班级。 class A
,class B
和class 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 ++编译器将抛出错误 我的问题是,为什么我们需要指定包含警卫?每个头文件只应包含一次并不常见吗?为什么编译器本身不处理多个包含?
答案 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++
最后一个代码就是编译器的工作原理。此时编译器无法帮助太多。
这是一个非常简单的例子,但我认为它可以帮到你。