包含保护:为什么C++编译器不自动只包含每个头文件一次?
Include Guards: Why doesn't the C++ compiler automatically include each header file once only?
使用头文件时,每个头文件只应包含一次。
例如,假设我有三个类。 class A
,class B
和class C
。
class A
在文件 A.h 中声明,class B
在文件 B.h 中声明,class C
在文件 C.h 中声明,它们在各自的 .cpp
文件中定义。
A.cpp
#include "A.h"
class A
{
}
在B.cpp
文件中,以下内容将是类的定义。
#include "A.h"
#include "B.h"
class B
{
A a;
}
C.cpp
文件也是如此。
#include "A.h"
#include "B.h"
#include "C.h"
class C
{
A a;
B b;
}
现在,如果包含保护未写入头文件中,则 g++ 编译器将引发错误。
我的问题是,为什么我们需要指定包含保护?每个头文件只应包含一次不是常识吗?为什么编译器不自己处理多个包含?
我的问题是,为什么我们需要指定包含保护?每个头文件只应包含一次不是常识吗?为什么编译器不自己处理多个包含?
因为并非所有标头都是如此。可以多次包含的标头的一个示例是 <assert>
标头,并且能够这样做实际上很重要。
尝试修复复制和粘贴文件内容的标头系统实际上没有任何意义。实际上,我们只需要转向更好的构建模型。
如前所述,有时您确实希望多次调用包含文件;并且在很多情况下,这可能是可取的。
这有用的一个例子是优化大型复杂模板的实例化。考虑一些典型的大型复杂模板类
template<typename T> class ComplicatedTemplate {
// ... Boring stuff goes here
};
在每个翻译单元中实例化和编译这个大模板,一遍又一遍地使用相同的模板类型,会变得非常陈旧。它减慢了编译速度,并且不必要地使每个对象模块膨胀,只是为了让链接器处理去除大量重复的模板实例化。这是很多浪费的工作。
许多编译器提供了控制模板实例化的方法。确切的细节有时会有所不同,但我将使用 gcc 使用的典型方法,您可以在此处阅读:
https://gcc.gnu.org/onlinedocs/gcc/Template-Instantiation.html
比如说,你想在实例化ComplicatedTemplate<std::vector<int>>
时消耗一些CPU时间,ComplicatedTemplate<std::vector<char>>
,也许ComplicatedTemplate<std::string<std::string>>
在一个名为"complex.cpp"的翻译单元中,并在头文件中声明它们extern。
好的,所以你最终会得到这个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
:
#include "complicated_template.H"
#define EXTERN
#include "complicated_template_inst.H"
现在,预实例化的模板实例列表位于一个位置。使用前面的示例,添加:
EXTERN template ComplicatedTemplate<std::vector<SomeCustomType>>;
具有防止在需要该模板实例的每个翻译单元中浪费此模板实例化的效果,以及在complicated.cpp
翻译单元中显式实例化它的效果。
您将在许多大型C++库中看到这种方法。他们通常会定义他们的模板,然后通过拉入一个单独的 #include 文件来预先实例化它们,该文件包含一些预处理器-fu。在拉入外部可见的头文件之后,实际的共享库还将第二次包含第二个文件,预处理器相应地进行装配,以将这些 extern 模板声明转换为模板实例化。
我们通常认为编译是一个步骤,但实际上涉及各种步骤,在C++的情况下,其中一个阶段是预处理,它处理#include <header.h>
的东西,它基本上将每个头文件的内容(以及类似 #define
(放在你的主文件中,所以,如果你不制定适当的条件,你的主源文件最终会得到重复的代码。
例如,假设您有两个文件:
// a.h
class A {
};
和
// b.cpp
#include "a.h"
#include "a.h"
int main()
{
return 0;
}
在实际编译之前,预处理器将处理b.cpp
,预处理器使用g++
结果如下所示:
# 1 "b.cpp"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "<command-line>" 2
# 1 "b.cpp"
# 1 "a.h" 1
class A {
};
# 2 "b.cpp" 2
# 1 "a.h" 1
class A { // Repeated code
};
# 3 "b.cpp" 2
int main()
{
return 0;
}
最后这段代码是编译器所处理的。在这一点上,编译器帮不上太多忙。
这是一个非常简单的例子,但我认为它可以帮助你。
- 在C++中一次将矢量值写入多个文件
- 在一次迭代中从 txt 文件中读取多行
- 在头文件和 cpp 文件中使用一次 #pragma 时出现结构重定义错误
- 如何使用C++一次读取整个二进制文件
- C++,从文件读取到结构,然后读取到向量(结构被推入向量太多次,而不仅仅是一次)
- 如何遍历几个每小时一次的根(.root)文件,并将它们组合成更大的每日数据.root文件?
- 调用函数一次用于动态链接库,一次从可执行文件调用函数
- ifstream不会在下一次迭代中打开文件
- 汇总然后平均 txt 文件中每 10 行一次
- 如何从文本文件中一次读取一个字符
- 一次包含一个 #include 表达式的多个头文件?
- 传输文件一次又一次地发送相同的字节
- 无法一次从二进制文件中读取一个 int
- 使用 #pragma 一次,#ifndef 在同一文件中包含保护
- 如何从 int 一次写入一个字节的文件?C++
- 在循环工作时,首先将两个文件读为向量,但仅次于迭代一次
- 一次读取一个字符的文件,直到使用 read(2) 和 write(2) 换行符
- 一次又一次地重建以查看导入的 QML 文件更改
- C++一次从文本文件中获取每 2 个整数作为对
- 包含保护:为什么C++编译器不自动只包含每个头文件一次?