为什么不包括防止递归包含和多个符号定义的内容?

时间:2022-02-21 15:08:08

Two common questions about include guards:

两个常见的问题包括警卫:

  1. FIRST QUESTION:

    第一个问题:

    Why aren't include guards protecting my header files from mutual, recursive inclusion? I keep getting errors about non-existing symbols which are obviously there or even weirder syntax errors every time I write something like the following:

    为什么不包括保护我的头文件不受相互、递归包含的保护?我经常会发现一些不存在的符号的错误,这些符号显然是存在的,甚至是更奇怪的语法错误,每次我写这样的东西:

    "a.h"

    “a.h”

    #ifndef A_H
    #define A_H
    
    #include "b.h"
    
    ...
    
    #endif // A_H
    

    "b.h"

    “b.h”

    #ifndef B_H
    #define B_H
    
    #include "a.h"
    
    ...
    
    #endif // B_H
    

    "main.cpp"

    “main.cpp”

    #include "a.h"
    int main()
    {
        ...
    }
    

    Why do I get errors compiling "main.cpp"? What do I need to do to solve my problem?

    为什么我要编译“main.cpp”?我需要做什么来解决我的问题?


  1. SECOND QUESTION:

    第二个问题:

    Why aren't include guards preventing multiple definitions? For instance, when my project contains two files that include the same header, sometimes the linker complains about some symbol being defined multiple times. For instance:

    为什么不包括防止多个定义的警卫?例如,当我的项目包含两个包含相同标题的文件时,有时链接器会抱怨某些符号被多次定义。例如:

    "header.h"

    “header.h”

    #ifndef HEADER_H
    #define HEADER_H
    
    int f()
    {
        return 0;
    }
    
    #endif // HEADER_H
    

    "source1.cpp"

    “source1.cpp”

    #include "header.h"
    ...
    

    "source2.cpp"

    “source2.cpp”

    #include "header.h"
    ...
    

    Why is this happening? What do I need to do to solve my problem?

    为什么会这样?我需要做什么来解决我的问题?

1 个解决方案

#1


119  

FIRST QUESTION:

第一个问题:

Why aren't include guards protecting my header files from mutual, recursive inclusion?

为什么不包括保护我的头文件不受相互、递归包含的保护?

They are.

他们是。

What they are not helping with is dependencies between the definitions of data structures in mutually-including headers. To see what this means, let's start with a basic scenario and see why include guards do help with mutual inclusions.

它们不帮助的是在mutuad -包括header中定义数据结构之间的依赖关系。为了了解这意味着什么,让我们从一个基本的场景开始,看看为什么包括警卫对相互包容的帮助。

Suppose your mutually including a.h and b.h header files have trivial content, i.e. the ellipses in the code sections from the question's text are replaced with the empty string. In this situation, your main.cpp will happily compile. And this is only thanks to your include guards!

假设你们彼此都包括a。h和b。h头文件的内容很琐碎,即从问题的文本中提取的代码段中的省略号被替换为空字符串。在这种情况下,你的主。cpp将愉快地编译。这只多亏了你的狱警!

If you're not convinced, try removing them:

如果你不相信,可以试着删除它们:

//================================================
// a.h

#include "b.h"

//================================================
// b.h

#include "a.h"

//================================================
// main.cpp
//
// Good luck getting this to compile...

#include "a.h"
int main()
{
    ...
}

You'll notice that the compiler will report a failure when it reaches the inclusion depth limit. This limit is implementation-specific. Per Paragraph 16.2/6 of the C++11 Standard:

您会注意到编译器在达到包含深度限制时将报告失败。这个限制是特定于实现的。C++11标准的第16.2/6段:

A #include preprocessing directive may appear in a source file that has been read because of a #include directive in another file, up to an implementation-defined nesting limit.

包括预处理指令可能出现在已经被读取的源文件中,因为在另一个文件中包含了指令,达到了实现定义的嵌套限制。

So what's going on?

那么发生了什么?

  1. When parsing main.cpp, the preprocessor will meet the directive #include "a.h". This directive tells the preprocessor to process the header file a.h, take the result of that processing, and replace the string #include "a.h" with that result;
  2. 当解析主要。cpp,预处理器将满足指令#include“a.h”。这个指令告诉预处理程序处理头文件a。h,取该处理的结果,并替换字符串#include“a”。h”的结果;
  3. While processing a.h, the preprocessor will meet the directive #include "b.h", and the same mechanism applies: the preprocessor shall process the header file b.h, take the result of its processing, and replace the #include directive with that result;
  4. 而处理。预处理器将满足指令#include“b”。同样的机制也适用:预处理器应该处理头文件b。h,取其处理的结果,并将#include指令替换为该结果;
  5. When processing b.h, the directive #include "a.h" will tell the preprocessor to process a.h and replace that directive with the result;
  6. 当处理b。h,指令号包括“a”。h会告诉预处理程序处理a。h并将该指令替换为结果;
  7. The preprocessor will start parsing a.h again, will meet the #include "b.h" directive again, and this will set up a potentially infinite recursive process. When reaching the critical nesting level, the compiler will report an error.
  8. 预处理器将开始解析a。再次,将会遇到#include“b”。再次指示,这将建立一个潜在的无限递归过程。当到达关键的嵌套级别时,编译器将报告一个错误。

When include guards are present, however, no infinite recursion will be set up in step 4. Let's see why:

但是,当包含警卫时,不会在步骤4中设置无限递归。让我们看看为什么:

  1. (same as before) When parsing main.cpp, the preprocessor will meet the directive #include "a.h". This tells the preprocessor to process the header file a.h, take the result of that processing, and replace the string #include "a.h" with that result;
  2. (和以前一样)解析main。cpp,预处理器将满足指令#include“a.h”。这告诉预处理程序处理头文件a。h,取该处理的结果,并替换字符串#include“a”。h”的结果;
  3. While processing a.h, the preprocessor will meet the directive #ifndef A_H. Since the macro A_H has not yet been defined, it will keep processing the following text. The subsequent directive (#defines A_H) defines the macro A_H. Then, the preprocessor will meet the directive #include "b.h": the preprocessor shall now process the header file b.h, take the result of its processing, and replace the #include directive with that result;
  4. 而处理。预处理器将满足指令#ifndef A_H。由于宏A_H尚未定义,它将继续处理以下文本。随后的指令(#define A_H)定义了宏A_H。然后,预处理程序将满足指令#include“b”。h:预处理器现在处理头文件b。h,取其处理的结果,并将#include指令替换为该结果;
  5. When processing b.h, the preprocessor will meet the directive #ifndef B_H. Since the macro B_H has not yet been defined, it will keep processing the following text. The subsequent directive (#defines B_H) defines the macro B_H. Then, the directive #include "a.h" will tell the preprocessor to process a.h and replace the #include directive in b.h with the result of preprocessing a.h;
  6. 当处理b。预处理器将满足指令#ifndef B_H。由于宏B_H尚未定义,它将继续处理下面的文本。随后的指令(#define B_H)定义了宏B_H。然后,指令号包括“a”。h会告诉预处理程序处理a。在b中替换#include指令。h由于预处理的结果;
  7. The compiler will start preprocessing a.h again, and meet the #ifndef A_H directive again. However, during previous preprocessing, macro A_H has been defined. Therefore, the compiler will skip the following text this time until the matching #endif directive is found, and the output of this processing is the empty string (supposing nothing follows the #endif directive, of course). The preprocessor will therefore replace the #include "a.h" directive in b.h with the empty string, and will trace back the execution until it replaces the original #include directive in main.cpp.
  8. 编译器将开始预处理a。再一次,再次遇到#ifndef A_H指令。然而,在之前的预处理过程中,已经定义了宏A_H。因此,在找到匹配的#endif指令之前,编译器将跳过下面的文本,而这个处理的输出是空字符串(当然,如果没有遵循#endif指令的话)。因此,预处理器将取代#include“a”。b h”指令。h与空字符串,并将跟踪执行直到它取代原来的#include指令在main.cpp中。

Thus, include guards do protect against mutual inclusion. However, they can't help with dependencies between the definitions of your classes in mutually-including files:

因此,包括守卫,防止相互包容。但是,它们不能帮助您的类的定义之间的依赖关系——包括文件:

//================================================
// a.h

#ifndef A_H
#define A_H

#include "b.h"

struct A
{
};

#endif // A_H

//================================================
// b.h

#ifndef B_H
#define B_H

#include "a.h"

struct B
{
    A* pA;
};

#endif // B_H

//================================================
// main.cpp
//
// Good luck getting this to compile...

#include "a.h"
int main()
{
    ...
}

Given the above headers, main.cpp will not compile.

考虑到上面的标题,main。cpp不会编译。

Why is this happening?

为什么会这样?

To see what's going on, it is enough to go through steps 1-4 again.

要想知道发生了什么,就可以再次经历第1-4步。

It is easy to see that the first three steps and most of the fourth step are unaffected by this change (just read through them to get convinced). However, something different happens at the end of step 4: after replacing the #include "a.h" directive in b.h with the empty string, the preprocessor will start parsing the content of b.h and, in particular, the definition of B. Unfortunately, the definition of B mentions class A, which has never been met before exactly because of the inclusion guards!

很容易看出,前三个步骤和第四个步骤的大部分都不受这一变化的影响(只是阅读它们以使其信服)。然而,在第4步结束时发生了一些不同的事情:替换了#include“a”。b h”指令。使用空字符串,预处理器将开始解析b的内容。h,特别是B的定义,B的定义是A类,这之前从来没有见过,因为有包含的守卫!

Declaring a member variable of a type which has not been previously declared is, of course, an error, and the compiler will politely point that out.

声明一个尚未声明的类型的成员变量当然是一个错误,编译器会礼貌地指出这一点。

What do I need to do to solve my problem?

我需要做什么来解决我的问题?

You need forward declarations.

你需要向前声明。

In fact, the definition of class A is not required in order to define class B, because a pointer to A is being declared as a member variable, and not an object of type A. Since pointers have fixed size, the compiler won't need to know the exact layout of A nor to compute its size in order to properly define class B. Hence, it is enough to forward-declare class A in b.h and make the compiler aware of its existence:

事实上,类的定义不是必需的,以定义类B,因为一个指向被声明为一个成员变量,而不是一个类型的对象A由于指针有固定大小,编译器不需要知道确切的布局和计算其大小以正确定义类B。因此,它足以提前声明类B。h使编译器意识到它的存在:

//================================================
// b.h

#ifndef B_H
#define B_H

// Forward declaration of A: no need to #include "a.h"
struct A;

struct B
{
    A* pA;
};

#endif // B_H

Your main.cpp will now certainly compile. A couple of remarks:

你的主。cpp现在肯定会编译。的备注:

  1. Not only breaking the mutual inclusion by replacing the #include directive with a forward declaration in b.h was enough to effectively express the dependency of B on A: using forward declarations whenever possible/practical is also considered to be a good programming practice, because it helps avoiding unnecessary inclusions, thus reducing the overall compilation time. However, after eliminating the mutual inclusion, main.cpp will have to be modified to #include both a.h and b.h (if the latter is needed at all), because b.h is no more indirectly #included through a.h;
  2. 不仅通过替换#include指令来破坏相互包含,还可以在b中使用正向声明。h足够有效地表达B对A的依赖:在可能的情况下使用forward声明也被认为是一种良好的编程实践,因为它有助于避免不必要的包含,从而减少整体的编译时间。然而,在消除了相互包容之后,main。cpp必须修改为#,包括a。h和b。h(如果需要的话),因为b。h不再是通过a.h间接包含的;
  3. While a forward declaration of class A is enough for the compiler to declare pointers to that class (or to use it in any other context where incomplete types are acceptable), dereferencing pointers to A (for instance to invoke a member function) or computing its size are illegal operations on incomplete types: if that is needed, the full definition of A needs to be available to the compiler, which means the header file that defines it must be included. This is why class definitions and the implementation of their member functions are usually split into a header file and an implementation file for that class (class templates are an exception to this rule): implementation files, which are never #included by other files in the project, can safely #include all the necessary headers to make definitions visible. Header files, on the other hand, won't #include other header files unless they really need to do so (for instance, to make the definition of a base class visible), and will use forward-declarations whenever possible/practical.
  4. 前置声明的类时,足以让编译器声明指针类(或使用它在任何其他上下文不完整类型都可以接受),取消对指向一种(例如调用成员函数)或计算其大小是非法的操作不完整类型:如果需要,需要的完整定义的编译器,这意味着定义必须包含的头文件。这就是为什么类定义和实现他们的成员函数通常分为头文件和实现文件的类(类模板是一个例外):实现文件,这是从来没有#包括其他文件的项目,可以安全地#包括所有必要的头文件定义可见。另一方面,头文件不会包含其他头文件,除非它们确实需要这样做(例如,为了使基类的定义可见),并且在可能的情况下使用向前声明。

SECOND QUESTION:

第二个问题:

Why aren't include guards preventing multiple definitions?

为什么不包括防止多个定义的警卫?

They are.

他们是。

What they are not protecting you from is multiple definitions in separate translation units. This is also explained in this Q&A on *.

他们不保护你的是独立翻译单元中的多重定义。这也可以在*的问答中解释。

Too see that, try removing the include guards and compiling the following, modified version of source1.cpp (or source2.cpp, for what it matters):

您也可以看到,尝试删除include守护程序,并编译下面的修改版本的source1。cpp(或source2。cpp,关于它的重要性):

//================================================
// source1.cpp
//
// Good luck getting this to compile...

#include "header.h"
#include "header.h"

int main()
{
    ...
}

The compiler will certainly complain here about f() being redefined. That's obvious: its definition is being included twice! However, the above source1.cpp will compile without problems when header.h contains the proper include guards. That's expected.

编译器肯定会抱怨f()被重新定义。这是显而易见的:它的定义被包含了两次!然而,上述source1。在header中,cpp将不会出现问题。h包含适当的包括警卫。这是预期。

Still, even when the include guards are present and the compiler will stop bothering you with error message, the linker will insist on the fact that multiple definitions being found when merging the object code obtained from the compilation of source1.cpp and source2.cpp, and will refuse to generate your executable.

不过,即使包含了包含的保护,编译器也不会再用错误消息来打扰您,链接器仍然会坚持在合并来自source1编译的对象代码时发现的多个定义。cpp和source2。cpp,并将拒绝生成您的可执行文件。

Why is this happening?

为什么会这样?

Basically, each .cpp file (the technical term in this context is translation unit) in your project is compiled separately and independently. When parsing a .cpp file, the preprocessor will process all the #include directives and expand all macro invocations it encounters, and the output of this pure text processing will be given in input to the compiler for translating it into object code. Once the compiler is done with producing the object code for one translation unit, it will proceed with the next one, and all the macro definitions that have been encountered while processing the previous translation unit will be forgotten.

基本上,每个.cpp文件(此上下文中的技术术语是翻译单元)都是独立编译的。在解析.cpp文件时,预处理器将处理所有的#include指令,并扩展它遇到的所有宏调用,并且将这个纯文本处理的输出输入到编译器中,以便将其转换为对象代码。一旦编译器完成了对一个翻译单元的对象代码的生成,它将继续进行下一个,并且在处理上一个翻译单元时所遇到的所有宏定义都将被遗忘。

In fact, compiling a project with n translation units (.cpp files) is like executing the same program (the compiler) n times, each time with a different input: different executions of the same program won't share the state of the previous program execution(s). Thus, each translation is performed independently and the preprocessor symbols encountered while compiling one translation unit will not be remembered when compiling other translation units (if you think about it for a moment, you will easily realize that this is actually a desirable behavior).

事实上,编译一个带有n个翻译单元的项目(。(cpp文件)就像执行相同的程序(编译器)n次,每次都有不同的输入:相同程序的不同执行不会共享上一个程序执行的状态。因此,每一个翻译都是独立完成的,编译一个翻译单元时所遇到的预处理符号,在编译其他翻译单元时将不会被记住(如果你思考一下,你会很容易地意识到这实际上是一种可取的行为)。

Therefore, even though include guards help you preventing recursive mutual inclusions and redundant inclusions of the same header in one translation unit, they can't detect whether the same definition is included in different translation unit.

因此,即使在一个翻译单元中包含了保护程序,可以帮助您防止相同标题的递归相互包含和冗余的包含,但是它们不能检测同一个定义是否包含在不同的翻译单元中。

Yet, when merging the object code generated from the compilation of all the .cpp files of your project, the linker will see that the same symbol is defined more than once, and since this violates the One Definition Rule. Per Paragraph 3.2/3 of the C++11 Standard:

然而,当将项目中所有.cpp文件的编译生成的对象代码合并时,链接器会发现同一个符号不止一次定义,而且这违反了一个定义规则。C++11标准的3.2/3:

Every program shall contain exactly one definition of every non-inline function or variable that is odr-used in that program; no diagnostic required. The definition can appear explicitly in the program, it can be found in the standard or a user-defined library, or (when appropriate) it is implicitly defined (see 12.1, 12.4 and 12.8). An inline function shall be defined in every translation unit in which it is odr-used.

每个程序都应该包含每个非内联函数或变量的一个定义,该函数或变量在该程序中使用;不需要诊断。该定义可以在程序中显式地出现,它可以在标准或用户定义的库中找到,或者(在适当的时候)被隐式定义(参见12.1、12.4和12.8)。一个内联函数应该在它所使用的每个翻译单元中定义。

Hence, the linker will emit an error and refuse to generate the executable of your program.

因此,链接器会发出错误并拒绝生成程序的可执行文件。

What do I need to do to solve my problem?

我需要做什么来解决我的问题?

If you want to keep your function definition in a header file that is #included by multiple translation units (notice, that no problem will arise if your header is #included just by one translation unit), you need to use the inline keyword.

如果您想要将函数定义保存在包含多个翻译单元的头文件中(请注意,如果您的头是由一个翻译单元包含的头,那么就不会出现问题),您需要使用内联关键字。

Otherwise, you need to keep only the declaration of your function in header.h, putting its definition (body) into one separate .cpp file only (this is the classical approach).

否则,您只需要在header中保留函数的声明。将其定义(主体)放入一个单独的.cpp文件中(这是经典的方法)。

The inline keyword represents a non-binding request to the compiler to inline the function's body directly at the call site, rather than setting up a stack frame for a regular function call. Although the compiler doesn't have to fulfill your request, the inline keyword does succeed in telling the linker to tolerate multiple symbol definitions. According to Paragraph 3.2/5 of the C++11 Standard:

内联关键字表示对编译器的一个非绑定请求,该请求直接在调用站点内内联该函数的主体,而不是为一个常规的函数调用设置一个堆栈框架。尽管编译器不需要满足您的请求,但内联关键字能够成功地告诉链接器容忍多个符号定义。根据C++11标准第3.2/5段:

There can be more than one definition of a class type (Clause 9), enumeration type (7.2), inline function with external linkage (7.1.2), class template (Clause 14), non-static function template (14.5.6), static data member of a class template (14.5.1.3), member function of a class template (14.5.1.1), or template specialization for which some template parameters are not specified (14.7, 14.5.5) in a program provided that each definition appears in a different translation unit, and provided the definitions satisfy the following requirements [...]

可以有不止一个类的定义(条款9)类型,枚举类型(7.2),与外部链接(7.1.2)内联函数,类模板(条款14),非静态函数模板(14.5.6)类的静态数据成员模板(14.5.1.3),类的成员函数模板(14.5.1.1),或模板特殊化的一些未指定模板参数(14.7,14.5.5),每个提供程序定义出现在不同的翻译单元,提供的定义满足以下要求[…]

The above Paragraph basically lists all the definitions which are commonly put in header files, because they can be safely included in multiple translation units. All other definitions with external linkage, instead, belong in source files.

上面的段落基本上列出了所有通常放在头文件中的定义,因为它们可以安全地包含在多个翻译单元中。所有其他带有外部链接的定义都属于源文件。

Using the static keyword instead of the inline keyword also results in suppressing linker errors by giving your function internal linkage, thus making each translation unit hold a private copy of that function (and of its local static variables). However, this eventually results in a larger executable, and the use of inline should be preferred in general.

使用静态关键字而不是内联关键字也会通过给函数内部链接来抑制链接错误,从而使每个翻译单元拥有该函数的私有副本(以及它的局部静态变量)。但是,这最终会导致一个更大的可执行文件,并且应该更倾向于使用内联。

An alternative way of achieving the same result as with the static keyword is to put function f() in an unnamed namespace. Per Paragraph 3.5/4 of the C++11 Standard:

实现与静态关键字相同的结果的另一种方法是将函数f()放入一个未命名的名称空间中。C++11标准的第3.5/4段:

An unnamed namespace or a namespace declared directly or indirectly within an unnamed namespace has internal linkage. All other namespaces have external linkage. A name having namespace scope that has not been given internal linkage above has the same linkage as the enclosing namespace if it is the name of:

一个未命名的名称空间或直接或间接在未命名的名称空间中声明的名称空间具有内部链接。所有其他名称空间都有外部链接。名称空间范围没有被赋予内部链接的名称,如果它是:

— a variable; or

——一个变量;或

a function; or

——一个函数;或

— a named class (Clause 9), or an unnamed class defined in a typedef declaration in which the class has the typedef name for linkage purposes (7.1.3); or

-一个被命名的类(第9条),或者定义在typedef声明中的一个未命名类,其中该类具有用于链接目的的typedef名称(7.1.3);或

— a named enumeration (7.2), or an unnamed enumeration defined in a typedef declaration in which the enumeration has the typedef name for linkage purposes (7.1.3); or

-一个命名的枚举(7.2),或在typedef声明中定义的未命名枚举,其中枚举具有用于链接目的的typedef名称(7.1.3);或

— an enumerator belonging to an enumeration with linkage; or

-具有链接的枚举数的枚举数;或

— a template.

——一个模板。

For the same reason mentioned above, the inline keyword should be preferred.

出于同样的原因,应该首选内联关键字。

#1


119  

FIRST QUESTION:

第一个问题:

Why aren't include guards protecting my header files from mutual, recursive inclusion?

为什么不包括保护我的头文件不受相互、递归包含的保护?

They are.

他们是。

What they are not helping with is dependencies between the definitions of data structures in mutually-including headers. To see what this means, let's start with a basic scenario and see why include guards do help with mutual inclusions.

它们不帮助的是在mutuad -包括header中定义数据结构之间的依赖关系。为了了解这意味着什么,让我们从一个基本的场景开始,看看为什么包括警卫对相互包容的帮助。

Suppose your mutually including a.h and b.h header files have trivial content, i.e. the ellipses in the code sections from the question's text are replaced with the empty string. In this situation, your main.cpp will happily compile. And this is only thanks to your include guards!

假设你们彼此都包括a。h和b。h头文件的内容很琐碎,即从问题的文本中提取的代码段中的省略号被替换为空字符串。在这种情况下,你的主。cpp将愉快地编译。这只多亏了你的狱警!

If you're not convinced, try removing them:

如果你不相信,可以试着删除它们:

//================================================
// a.h

#include "b.h"

//================================================
// b.h

#include "a.h"

//================================================
// main.cpp
//
// Good luck getting this to compile...

#include "a.h"
int main()
{
    ...
}

You'll notice that the compiler will report a failure when it reaches the inclusion depth limit. This limit is implementation-specific. Per Paragraph 16.2/6 of the C++11 Standard:

您会注意到编译器在达到包含深度限制时将报告失败。这个限制是特定于实现的。C++11标准的第16.2/6段:

A #include preprocessing directive may appear in a source file that has been read because of a #include directive in another file, up to an implementation-defined nesting limit.

包括预处理指令可能出现在已经被读取的源文件中,因为在另一个文件中包含了指令,达到了实现定义的嵌套限制。

So what's going on?

那么发生了什么?

  1. When parsing main.cpp, the preprocessor will meet the directive #include "a.h". This directive tells the preprocessor to process the header file a.h, take the result of that processing, and replace the string #include "a.h" with that result;
  2. 当解析主要。cpp,预处理器将满足指令#include“a.h”。这个指令告诉预处理程序处理头文件a。h,取该处理的结果,并替换字符串#include“a”。h”的结果;
  3. While processing a.h, the preprocessor will meet the directive #include "b.h", and the same mechanism applies: the preprocessor shall process the header file b.h, take the result of its processing, and replace the #include directive with that result;
  4. 而处理。预处理器将满足指令#include“b”。同样的机制也适用:预处理器应该处理头文件b。h,取其处理的结果,并将#include指令替换为该结果;
  5. When processing b.h, the directive #include "a.h" will tell the preprocessor to process a.h and replace that directive with the result;
  6. 当处理b。h,指令号包括“a”。h会告诉预处理程序处理a。h并将该指令替换为结果;
  7. The preprocessor will start parsing a.h again, will meet the #include "b.h" directive again, and this will set up a potentially infinite recursive process. When reaching the critical nesting level, the compiler will report an error.
  8. 预处理器将开始解析a。再次,将会遇到#include“b”。再次指示,这将建立一个潜在的无限递归过程。当到达关键的嵌套级别时,编译器将报告一个错误。

When include guards are present, however, no infinite recursion will be set up in step 4. Let's see why:

但是,当包含警卫时,不会在步骤4中设置无限递归。让我们看看为什么:

  1. (same as before) When parsing main.cpp, the preprocessor will meet the directive #include "a.h". This tells the preprocessor to process the header file a.h, take the result of that processing, and replace the string #include "a.h" with that result;
  2. (和以前一样)解析main。cpp,预处理器将满足指令#include“a.h”。这告诉预处理程序处理头文件a。h,取该处理的结果,并替换字符串#include“a”。h”的结果;
  3. While processing a.h, the preprocessor will meet the directive #ifndef A_H. Since the macro A_H has not yet been defined, it will keep processing the following text. The subsequent directive (#defines A_H) defines the macro A_H. Then, the preprocessor will meet the directive #include "b.h": the preprocessor shall now process the header file b.h, take the result of its processing, and replace the #include directive with that result;
  4. 而处理。预处理器将满足指令#ifndef A_H。由于宏A_H尚未定义,它将继续处理以下文本。随后的指令(#define A_H)定义了宏A_H。然后,预处理程序将满足指令#include“b”。h:预处理器现在处理头文件b。h,取其处理的结果,并将#include指令替换为该结果;
  5. When processing b.h, the preprocessor will meet the directive #ifndef B_H. Since the macro B_H has not yet been defined, it will keep processing the following text. The subsequent directive (#defines B_H) defines the macro B_H. Then, the directive #include "a.h" will tell the preprocessor to process a.h and replace the #include directive in b.h with the result of preprocessing a.h;
  6. 当处理b。预处理器将满足指令#ifndef B_H。由于宏B_H尚未定义,它将继续处理下面的文本。随后的指令(#define B_H)定义了宏B_H。然后,指令号包括“a”。h会告诉预处理程序处理a。在b中替换#include指令。h由于预处理的结果;
  7. The compiler will start preprocessing a.h again, and meet the #ifndef A_H directive again. However, during previous preprocessing, macro A_H has been defined. Therefore, the compiler will skip the following text this time until the matching #endif directive is found, and the output of this processing is the empty string (supposing nothing follows the #endif directive, of course). The preprocessor will therefore replace the #include "a.h" directive in b.h with the empty string, and will trace back the execution until it replaces the original #include directive in main.cpp.
  8. 编译器将开始预处理a。再一次,再次遇到#ifndef A_H指令。然而,在之前的预处理过程中,已经定义了宏A_H。因此,在找到匹配的#endif指令之前,编译器将跳过下面的文本,而这个处理的输出是空字符串(当然,如果没有遵循#endif指令的话)。因此,预处理器将取代#include“a”。b h”指令。h与空字符串,并将跟踪执行直到它取代原来的#include指令在main.cpp中。

Thus, include guards do protect against mutual inclusion. However, they can't help with dependencies between the definitions of your classes in mutually-including files:

因此,包括守卫,防止相互包容。但是,它们不能帮助您的类的定义之间的依赖关系——包括文件:

//================================================
// a.h

#ifndef A_H
#define A_H

#include "b.h"

struct A
{
};

#endif // A_H

//================================================
// b.h

#ifndef B_H
#define B_H

#include "a.h"

struct B
{
    A* pA;
};

#endif // B_H

//================================================
// main.cpp
//
// Good luck getting this to compile...

#include "a.h"
int main()
{
    ...
}

Given the above headers, main.cpp will not compile.

考虑到上面的标题,main。cpp不会编译。

Why is this happening?

为什么会这样?

To see what's going on, it is enough to go through steps 1-4 again.

要想知道发生了什么,就可以再次经历第1-4步。

It is easy to see that the first three steps and most of the fourth step are unaffected by this change (just read through them to get convinced). However, something different happens at the end of step 4: after replacing the #include "a.h" directive in b.h with the empty string, the preprocessor will start parsing the content of b.h and, in particular, the definition of B. Unfortunately, the definition of B mentions class A, which has never been met before exactly because of the inclusion guards!

很容易看出,前三个步骤和第四个步骤的大部分都不受这一变化的影响(只是阅读它们以使其信服)。然而,在第4步结束时发生了一些不同的事情:替换了#include“a”。b h”指令。使用空字符串,预处理器将开始解析b的内容。h,特别是B的定义,B的定义是A类,这之前从来没有见过,因为有包含的守卫!

Declaring a member variable of a type which has not been previously declared is, of course, an error, and the compiler will politely point that out.

声明一个尚未声明的类型的成员变量当然是一个错误,编译器会礼貌地指出这一点。

What do I need to do to solve my problem?

我需要做什么来解决我的问题?

You need forward declarations.

你需要向前声明。

In fact, the definition of class A is not required in order to define class B, because a pointer to A is being declared as a member variable, and not an object of type A. Since pointers have fixed size, the compiler won't need to know the exact layout of A nor to compute its size in order to properly define class B. Hence, it is enough to forward-declare class A in b.h and make the compiler aware of its existence:

事实上,类的定义不是必需的,以定义类B,因为一个指向被声明为一个成员变量,而不是一个类型的对象A由于指针有固定大小,编译器不需要知道确切的布局和计算其大小以正确定义类B。因此,它足以提前声明类B。h使编译器意识到它的存在:

//================================================
// b.h

#ifndef B_H
#define B_H

// Forward declaration of A: no need to #include "a.h"
struct A;

struct B
{
    A* pA;
};

#endif // B_H

Your main.cpp will now certainly compile. A couple of remarks:

你的主。cpp现在肯定会编译。的备注:

  1. Not only breaking the mutual inclusion by replacing the #include directive with a forward declaration in b.h was enough to effectively express the dependency of B on A: using forward declarations whenever possible/practical is also considered to be a good programming practice, because it helps avoiding unnecessary inclusions, thus reducing the overall compilation time. However, after eliminating the mutual inclusion, main.cpp will have to be modified to #include both a.h and b.h (if the latter is needed at all), because b.h is no more indirectly #included through a.h;
  2. 不仅通过替换#include指令来破坏相互包含,还可以在b中使用正向声明。h足够有效地表达B对A的依赖:在可能的情况下使用forward声明也被认为是一种良好的编程实践,因为它有助于避免不必要的包含,从而减少整体的编译时间。然而,在消除了相互包容之后,main。cpp必须修改为#,包括a。h和b。h(如果需要的话),因为b。h不再是通过a.h间接包含的;
  3. While a forward declaration of class A is enough for the compiler to declare pointers to that class (or to use it in any other context where incomplete types are acceptable), dereferencing pointers to A (for instance to invoke a member function) or computing its size are illegal operations on incomplete types: if that is needed, the full definition of A needs to be available to the compiler, which means the header file that defines it must be included. This is why class definitions and the implementation of their member functions are usually split into a header file and an implementation file for that class (class templates are an exception to this rule): implementation files, which are never #included by other files in the project, can safely #include all the necessary headers to make definitions visible. Header files, on the other hand, won't #include other header files unless they really need to do so (for instance, to make the definition of a base class visible), and will use forward-declarations whenever possible/practical.
  4. 前置声明的类时,足以让编译器声明指针类(或使用它在任何其他上下文不完整类型都可以接受),取消对指向一种(例如调用成员函数)或计算其大小是非法的操作不完整类型:如果需要,需要的完整定义的编译器,这意味着定义必须包含的头文件。这就是为什么类定义和实现他们的成员函数通常分为头文件和实现文件的类(类模板是一个例外):实现文件,这是从来没有#包括其他文件的项目,可以安全地#包括所有必要的头文件定义可见。另一方面,头文件不会包含其他头文件,除非它们确实需要这样做(例如,为了使基类的定义可见),并且在可能的情况下使用向前声明。

SECOND QUESTION:

第二个问题:

Why aren't include guards preventing multiple definitions?

为什么不包括防止多个定义的警卫?

They are.

他们是。

What they are not protecting you from is multiple definitions in separate translation units. This is also explained in this Q&A on *.

他们不保护你的是独立翻译单元中的多重定义。这也可以在*的问答中解释。

Too see that, try removing the include guards and compiling the following, modified version of source1.cpp (or source2.cpp, for what it matters):

您也可以看到,尝试删除include守护程序,并编译下面的修改版本的source1。cpp(或source2。cpp,关于它的重要性):

//================================================
// source1.cpp
//
// Good luck getting this to compile...

#include "header.h"
#include "header.h"

int main()
{
    ...
}

The compiler will certainly complain here about f() being redefined. That's obvious: its definition is being included twice! However, the above source1.cpp will compile without problems when header.h contains the proper include guards. That's expected.

编译器肯定会抱怨f()被重新定义。这是显而易见的:它的定义被包含了两次!然而,上述source1。在header中,cpp将不会出现问题。h包含适当的包括警卫。这是预期。

Still, even when the include guards are present and the compiler will stop bothering you with error message, the linker will insist on the fact that multiple definitions being found when merging the object code obtained from the compilation of source1.cpp and source2.cpp, and will refuse to generate your executable.

不过,即使包含了包含的保护,编译器也不会再用错误消息来打扰您,链接器仍然会坚持在合并来自source1编译的对象代码时发现的多个定义。cpp和source2。cpp,并将拒绝生成您的可执行文件。

Why is this happening?

为什么会这样?

Basically, each .cpp file (the technical term in this context is translation unit) in your project is compiled separately and independently. When parsing a .cpp file, the preprocessor will process all the #include directives and expand all macro invocations it encounters, and the output of this pure text processing will be given in input to the compiler for translating it into object code. Once the compiler is done with producing the object code for one translation unit, it will proceed with the next one, and all the macro definitions that have been encountered while processing the previous translation unit will be forgotten.

基本上,每个.cpp文件(此上下文中的技术术语是翻译单元)都是独立编译的。在解析.cpp文件时,预处理器将处理所有的#include指令,并扩展它遇到的所有宏调用,并且将这个纯文本处理的输出输入到编译器中,以便将其转换为对象代码。一旦编译器完成了对一个翻译单元的对象代码的生成,它将继续进行下一个,并且在处理上一个翻译单元时所遇到的所有宏定义都将被遗忘。

In fact, compiling a project with n translation units (.cpp files) is like executing the same program (the compiler) n times, each time with a different input: different executions of the same program won't share the state of the previous program execution(s). Thus, each translation is performed independently and the preprocessor symbols encountered while compiling one translation unit will not be remembered when compiling other translation units (if you think about it for a moment, you will easily realize that this is actually a desirable behavior).

事实上,编译一个带有n个翻译单元的项目(。(cpp文件)就像执行相同的程序(编译器)n次,每次都有不同的输入:相同程序的不同执行不会共享上一个程序执行的状态。因此,每一个翻译都是独立完成的,编译一个翻译单元时所遇到的预处理符号,在编译其他翻译单元时将不会被记住(如果你思考一下,你会很容易地意识到这实际上是一种可取的行为)。

Therefore, even though include guards help you preventing recursive mutual inclusions and redundant inclusions of the same header in one translation unit, they can't detect whether the same definition is included in different translation unit.

因此,即使在一个翻译单元中包含了保护程序,可以帮助您防止相同标题的递归相互包含和冗余的包含,但是它们不能检测同一个定义是否包含在不同的翻译单元中。

Yet, when merging the object code generated from the compilation of all the .cpp files of your project, the linker will see that the same symbol is defined more than once, and since this violates the One Definition Rule. Per Paragraph 3.2/3 of the C++11 Standard:

然而,当将项目中所有.cpp文件的编译生成的对象代码合并时,链接器会发现同一个符号不止一次定义,而且这违反了一个定义规则。C++11标准的3.2/3:

Every program shall contain exactly one definition of every non-inline function or variable that is odr-used in that program; no diagnostic required. The definition can appear explicitly in the program, it can be found in the standard or a user-defined library, or (when appropriate) it is implicitly defined (see 12.1, 12.4 and 12.8). An inline function shall be defined in every translation unit in which it is odr-used.

每个程序都应该包含每个非内联函数或变量的一个定义,该函数或变量在该程序中使用;不需要诊断。该定义可以在程序中显式地出现,它可以在标准或用户定义的库中找到,或者(在适当的时候)被隐式定义(参见12.1、12.4和12.8)。一个内联函数应该在它所使用的每个翻译单元中定义。

Hence, the linker will emit an error and refuse to generate the executable of your program.

因此,链接器会发出错误并拒绝生成程序的可执行文件。

What do I need to do to solve my problem?

我需要做什么来解决我的问题?

If you want to keep your function definition in a header file that is #included by multiple translation units (notice, that no problem will arise if your header is #included just by one translation unit), you need to use the inline keyword.

如果您想要将函数定义保存在包含多个翻译单元的头文件中(请注意,如果您的头是由一个翻译单元包含的头,那么就不会出现问题),您需要使用内联关键字。

Otherwise, you need to keep only the declaration of your function in header.h, putting its definition (body) into one separate .cpp file only (this is the classical approach).

否则,您只需要在header中保留函数的声明。将其定义(主体)放入一个单独的.cpp文件中(这是经典的方法)。

The inline keyword represents a non-binding request to the compiler to inline the function's body directly at the call site, rather than setting up a stack frame for a regular function call. Although the compiler doesn't have to fulfill your request, the inline keyword does succeed in telling the linker to tolerate multiple symbol definitions. According to Paragraph 3.2/5 of the C++11 Standard:

内联关键字表示对编译器的一个非绑定请求,该请求直接在调用站点内内联该函数的主体,而不是为一个常规的函数调用设置一个堆栈框架。尽管编译器不需要满足您的请求,但内联关键字能够成功地告诉链接器容忍多个符号定义。根据C++11标准第3.2/5段:

There can be more than one definition of a class type (Clause 9), enumeration type (7.2), inline function with external linkage (7.1.2), class template (Clause 14), non-static function template (14.5.6), static data member of a class template (14.5.1.3), member function of a class template (14.5.1.1), or template specialization for which some template parameters are not specified (14.7, 14.5.5) in a program provided that each definition appears in a different translation unit, and provided the definitions satisfy the following requirements [...]

可以有不止一个类的定义(条款9)类型,枚举类型(7.2),与外部链接(7.1.2)内联函数,类模板(条款14),非静态函数模板(14.5.6)类的静态数据成员模板(14.5.1.3),类的成员函数模板(14.5.1.1),或模板特殊化的一些未指定模板参数(14.7,14.5.5),每个提供程序定义出现在不同的翻译单元,提供的定义满足以下要求[…]

The above Paragraph basically lists all the definitions which are commonly put in header files, because they can be safely included in multiple translation units. All other definitions with external linkage, instead, belong in source files.

上面的段落基本上列出了所有通常放在头文件中的定义,因为它们可以安全地包含在多个翻译单元中。所有其他带有外部链接的定义都属于源文件。

Using the static keyword instead of the inline keyword also results in suppressing linker errors by giving your function internal linkage, thus making each translation unit hold a private copy of that function (and of its local static variables). However, this eventually results in a larger executable, and the use of inline should be preferred in general.

使用静态关键字而不是内联关键字也会通过给函数内部链接来抑制链接错误,从而使每个翻译单元拥有该函数的私有副本(以及它的局部静态变量)。但是,这最终会导致一个更大的可执行文件,并且应该更倾向于使用内联。

An alternative way of achieving the same result as with the static keyword is to put function f() in an unnamed namespace. Per Paragraph 3.5/4 of the C++11 Standard:

实现与静态关键字相同的结果的另一种方法是将函数f()放入一个未命名的名称空间中。C++11标准的第3.5/4段:

An unnamed namespace or a namespace declared directly or indirectly within an unnamed namespace has internal linkage. All other namespaces have external linkage. A name having namespace scope that has not been given internal linkage above has the same linkage as the enclosing namespace if it is the name of:

一个未命名的名称空间或直接或间接在未命名的名称空间中声明的名称空间具有内部链接。所有其他名称空间都有外部链接。名称空间范围没有被赋予内部链接的名称,如果它是:

— a variable; or

——一个变量;或

a function; or

——一个函数;或

— a named class (Clause 9), or an unnamed class defined in a typedef declaration in which the class has the typedef name for linkage purposes (7.1.3); or

-一个被命名的类(第9条),或者定义在typedef声明中的一个未命名类,其中该类具有用于链接目的的typedef名称(7.1.3);或

— a named enumeration (7.2), or an unnamed enumeration defined in a typedef declaration in which the enumeration has the typedef name for linkage purposes (7.1.3); or

-一个命名的枚举(7.2),或在typedef声明中定义的未命名枚举,其中枚举具有用于链接目的的typedef名称(7.1.3);或

— an enumerator belonging to an enumeration with linkage; or

-具有链接的枚举数的枚举数;或

— a template.

——一个模板。

For the same reason mentioned above, the inline keyword should be preferred.

出于同样的原因,应该首选内联关键字。