包括警卫:为什么c++编译器不能只自动包含每个头文件?(复制)

时间:2021-11-03 04:40:16

This question already has an answer here:

这个问题已经有了答案:

While using header files, each header file should be included only once.
For example, lets say I have three classes. class A, class B and class C.

在使用头文件时,每个头文件应该只包含一次。例如,假设我有三个类。A类,B类,C类。

class A is declared in the file A.h, class B is declared in the file B.h and class C is declared in the file C.h, and they are defined in their respective .cpp files.
A.cpp

类A在文件A中声明。h, B类在文件B中声明。h和C类在文件C中声明。h,它们在各自的.cpp文件中定义。A.cpp

#include "A.h"
class A  
{  
}  

In the B.cpp file, the following will be the definition of the class.

在B。cpp文件,下面将是这个类的定义。

#include "A.h"
#include "B.h"
class B  
{  
   A a;
}

And same goes for the C.cpp file as well.

C也是一样。cpp文件。

#include "A.h"
#include "B.h"
#include "C.h"
class C  
{  
  A a;  
  B b;  
}  

Now, if include guards are not written in the header files, then the g++ compiler will throw an error.
My question is, why should we need to specify the include guards? Is it not common sense that each header file should be included only once? Why doesn't the compiler take care of multiple includes by itself?

现在,如果在头文件中没有写入保护,那么g++编译器将抛出一个错误。我的问题是,为什么我们需要指定包括警卫?每个头文件只包含一次,这不是常识吗?为什么编译器不能自己处理多个include ?

3 个解决方案

#1


2  

My question is, why should we need to specify the include guards? Is it not common sense that each header file should be included only once? Why doesn't the compiler take care of multiple includes by itself?

我的问题是,为什么我们需要指定包括警卫?每个头文件只包含一次,这不是常识吗?为什么编译器不能自己处理多个include ?

Because it's not true for all headers. One example of a header that can be included more than once, and being able to do so actually matters, is the <assert> header.

因为对于所有的标题都不是这样。一个可以包含不止一次的头的例子,并且能够这么做实际上很重要,是 header。


There's not really any point to trying to fix up the header system of copying and pasting file contents. Really we just need to move to a better build model.

试图修复复制和粘贴文件内容的头系统并没有什么意义。实际上,我们只需要转移到一个更好的构建模型。

#2


2  

As its been said, sometimes you do want an include file to be invoked more than once; and there may be many situations where this is desirable.

正如所述,有时您确实希望一个包含文件被多次调用;在许多情况下这是可取的。

One example where this comes in useful is for optimizing instantiations of large, complicated templates. Consider some typical large, complicated template class

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

template<typename T> class ComplicatedTemplate {

// ... Boring stuff goes here

};

Having this large template instantiated and compiled in every translation unit, for the same template type, over and over again, gets really old. It slows down a compile, and needlessly bloats every object module, only to have the linker deal with stripping out a ton of duplicate template instantiations. It's a lot of wasted work.

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

Many compilers offer ways to control template instantiations. The exact details can vary, sometimes, but I'll use the typical approach, as used by gcc, which you can read about here:

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

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

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

Say, you'd want to burn some CPU time instantiating ComplicatedTemplate<std::vector<int>>, ComplicatedTemplate<std::vector<char>>, maybe ComplicatedTemplate<std::string<std::string>> in some translation unit called "complicated.cpp", and just declare them extern in the header file.

比方说,你想要烧一些CPU时间来实例化复杂的模板 <:vector> >,复杂的模板 <:> >,可能是复杂的模板 <:string> >在一些翻译单元中被称为“复杂”。并在头文件中声明它们。 <:string>向量

Ok, so you end up with this in 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>>;

Then, in complicated.cpp:

然后,在complicated.cpp:

#include "complicated_template.H"

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

Ok, so that's going to work fine, except for one inconvenience. If you decide to also add ComplicatedTemplate<std::vector<SomeCustomType>>, or anything else, to the list of pre-instantiated templates, this needs to be done in two places; both in the header file, and in complicated.cpp

好的,这样就没问题了,除了一个不便之处。如果您决定添加复杂的模板 <:vector> >,或其他任何东西,到预实例化模板的列表中,这需要在两个地方完成;在头文件中,在复杂的。cpp中。

Here's a typical approach that eliminates this duplication:

这里有一个典型的方法可以消除这种重复:

complicated_template.H:

complicated_template.H:

template<typename T> class ComplicatedTemplate {

// ... Boring stuff goes here

};

#include "complicated_template_inst.H"

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>>;

Then, in complicated.cpp:

然后,在complicated.cpp:

#include "complicated_template.H"
#define EXTERN
#include "complicated_template_inst.H"

Now, the list of template instances that get pre-instantiated is in one place. Using the earlier example, adding:

现在,得到预实例化的模板实例的列表在一个地方。使用前面的示例,添加:

EXTERN template ComplicatedTemplate<std::vector<SomeCustomType>>;

has the effect of both preventing the wasted instantiation of this template in every translation unit that needs that template instance, and of instantiating it explicitly in the complicated.cpp translation unit.

它的作用是防止在每个需要模板实例的翻译单元中对该模板的浪费实例化,并且在复杂的情况下显式地实例化它。cpp翻译单元。

You will see this kind of an approach in many large C++ libraries. They will typically define their templates, then pre-instantiating them by pulling in a separate #include file, that contains some preprocessor-fu. The actual shared library will also include the second file a second time, after pulling in the externally-visible header file, with the preprocessor accordingly rigged up to turn those extern template declarations into template instantiations.

在许多大型c++库中,您将看到这种方法。他们通常会定义自己的模板,然后通过拉入一个单独的#include文件来预先实例化这些模板,其中包含一些预处理程序。实际的共享库也将在引入外部可见的头文件之后,第二次包含第二个文件,并使用预处理器将这些外部模板声明转换为模板实例化。

#3


0  

We usually think of the compilation as a single step, but actually there are various steps involved, in the case of C++, one of those phases is the preprocessing, which handles the #include <header.h> stuff, which basically puts the contents of each header file (and also things like #define) in your main file, so, if you do not make the appropriate conditions your main source file will end up with repeated code.

我们通常认为编译是一个单独的步骤,但是实际上有很多步骤,在c++中,其中一个阶段是预处理,处理#include

的内容,主要是将每个头文件的内容(以及#define之类的东西)放在你的主文件中,因此,如果你没有适当的条件,你的主源文件将会以重复的代码结束。
。h>

For example, let's say you have two files:

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

// a.h
class A {

};

And

// b.cpp
#include "a.h"
#include "a.h"

int main()
{
    return 0;
}

Before the actual compilation b.cpp will be processed by the preprocessor, which using g++ the result is something like this:

在实际编译之前。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;
}

And this last code is what the compiler works on. At this point the compiler can't help too much.

最后一个代码是编译器的工作。此时,编译器不能提供太多帮助。

This is a very simplistic example but I think it can help you.

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

#1


2  

My question is, why should we need to specify the include guards? Is it not common sense that each header file should be included only once? Why doesn't the compiler take care of multiple includes by itself?

我的问题是,为什么我们需要指定包括警卫?每个头文件只包含一次,这不是常识吗?为什么编译器不能自己处理多个include ?

Because it's not true for all headers. One example of a header that can be included more than once, and being able to do so actually matters, is the <assert> header.

因为对于所有的标题都不是这样。一个可以包含不止一次的头的例子,并且能够这么做实际上很重要,是 header。


There's not really any point to trying to fix up the header system of copying and pasting file contents. Really we just need to move to a better build model.

试图修复复制和粘贴文件内容的头系统并没有什么意义。实际上,我们只需要转移到一个更好的构建模型。

#2


2  

As its been said, sometimes you do want an include file to be invoked more than once; and there may be many situations where this is desirable.

正如所述,有时您确实希望一个包含文件被多次调用;在许多情况下这是可取的。

One example where this comes in useful is for optimizing instantiations of large, complicated templates. Consider some typical large, complicated template class

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

template<typename T> class ComplicatedTemplate {

// ... Boring stuff goes here

};

Having this large template instantiated and compiled in every translation unit, for the same template type, over and over again, gets really old. It slows down a compile, and needlessly bloats every object module, only to have the linker deal with stripping out a ton of duplicate template instantiations. It's a lot of wasted work.

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

Many compilers offer ways to control template instantiations. The exact details can vary, sometimes, but I'll use the typical approach, as used by gcc, which you can read about here:

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

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

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

Say, you'd want to burn some CPU time instantiating ComplicatedTemplate<std::vector<int>>, ComplicatedTemplate<std::vector<char>>, maybe ComplicatedTemplate<std::string<std::string>> in some translation unit called "complicated.cpp", and just declare them extern in the header file.

比方说,你想要烧一些CPU时间来实例化复杂的模板 <:vector> >,复杂的模板 <:> >,可能是复杂的模板 <:string> >在一些翻译单元中被称为“复杂”。并在头文件中声明它们。 <:string>向量

Ok, so you end up with this in 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>>;

Then, in complicated.cpp:

然后,在complicated.cpp:

#include "complicated_template.H"

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

Ok, so that's going to work fine, except for one inconvenience. If you decide to also add ComplicatedTemplate<std::vector<SomeCustomType>>, or anything else, to the list of pre-instantiated templates, this needs to be done in two places; both in the header file, and in complicated.cpp

好的,这样就没问题了,除了一个不便之处。如果您决定添加复杂的模板 <:vector> >,或其他任何东西,到预实例化模板的列表中,这需要在两个地方完成;在头文件中,在复杂的。cpp中。

Here's a typical approach that eliminates this duplication:

这里有一个典型的方法可以消除这种重复:

complicated_template.H:

complicated_template.H:

template<typename T> class ComplicatedTemplate {

// ... Boring stuff goes here

};

#include "complicated_template_inst.H"

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>>;

Then, in complicated.cpp:

然后,在complicated.cpp:

#include "complicated_template.H"
#define EXTERN
#include "complicated_template_inst.H"

Now, the list of template instances that get pre-instantiated is in one place. Using the earlier example, adding:

现在,得到预实例化的模板实例的列表在一个地方。使用前面的示例,添加:

EXTERN template ComplicatedTemplate<std::vector<SomeCustomType>>;

has the effect of both preventing the wasted instantiation of this template in every translation unit that needs that template instance, and of instantiating it explicitly in the complicated.cpp translation unit.

它的作用是防止在每个需要模板实例的翻译单元中对该模板的浪费实例化,并且在复杂的情况下显式地实例化它。cpp翻译单元。

You will see this kind of an approach in many large C++ libraries. They will typically define their templates, then pre-instantiating them by pulling in a separate #include file, that contains some preprocessor-fu. The actual shared library will also include the second file a second time, after pulling in the externally-visible header file, with the preprocessor accordingly rigged up to turn those extern template declarations into template instantiations.

在许多大型c++库中,您将看到这种方法。他们通常会定义自己的模板,然后通过拉入一个单独的#include文件来预先实例化这些模板,其中包含一些预处理程序。实际的共享库也将在引入外部可见的头文件之后,第二次包含第二个文件,并使用预处理器将这些外部模板声明转换为模板实例化。

#3


0  

We usually think of the compilation as a single step, but actually there are various steps involved, in the case of C++, one of those phases is the preprocessing, which handles the #include <header.h> stuff, which basically puts the contents of each header file (and also things like #define) in your main file, so, if you do not make the appropriate conditions your main source file will end up with repeated code.

我们通常认为编译是一个单独的步骤,但是实际上有很多步骤,在c++中,其中一个阶段是预处理,处理#include

的内容,主要是将每个头文件的内容(以及#define之类的东西)放在你的主文件中,因此,如果你没有适当的条件,你的主源文件将会以重复的代码结束。
。h>

For example, let's say you have two files:

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

// a.h
class A {

};

And

// b.cpp
#include "a.h"
#include "a.h"

int main()
{
    return 0;
}

Before the actual compilation b.cpp will be processed by the preprocessor, which using g++ the result is something like this:

在实际编译之前。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;
}

And this last code is what the compiler works on. At this point the compiler can't help too much.

最后一个代码是编译器的工作。此时,编译器不能提供太多帮助。

This is a very simplistic example but I think it can help you.

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