为什么要把整个报头内容放在保护令牌中

Why to put the entire header content within guard tokens?

本文关键字:保护 令牌 报头 为什么      更新时间:2023-10-16

C和c++区分声明和定义。

你可以多次声明一个符号,但是你只能定义它一次。通过学习这个,我有一个想法,把声明放在守卫之外,而定义放在守卫之内:

// declarations
int foo(int x, int y);
int bar(int x, int y);
extern double something;
class X;
#ifndef _MY_HEADER_H_
#define _MY_HEADER_H_
#include "otherheader1.h"
#include "otherheader2.h"
// definitions of structures, classes.
class X
{
    // blah blah...
};
#endif

通过这种方式,我可以按照我想要的顺序包含我的标题。也许循环依赖不会成为问题。

那么,如果我们可以把声明放在外面,为什么要用保护令牌保护整个头文件呢?

我的理由如下:

当两个标头以某种方式相互引用时,您可能经常遇到问题。您通常会得到一个未声明的符号错误,您的第一反应是包含必要的标头。但是当你的两个头文件碰巧相互包含时,你会得到一个神秘的错误。

a.h:

#ifndef A_H
#define A_H
#include "b.h"
class A {B *b;}       
#endif

b.h

#ifndef B_H
#define B_H
#include "a.h"
class B {A *a;}       
#endif

当在b.p cpp中包含b.h时,在a.h中会得到一个错误,即没有声明B,但包含了头文件。(这是一个wtf时刻。)

这是因为头守卫不会嵌套:

#ifndef B_H
#define B_H
#ifndef A_H
#define A_H
// B_H already defined no include here.
class A {B *b;}       
#endif
class B {A *a;}       
#endif

如果你把声明放在保护之外,你可以防止这种情况发生:

class B; // in b.h
#ifndef B_H
#define B_H
class A; // in a.h
#ifndef A_H
#define A_H
class B; // again from b.h
// B_H already defined no include here, no redefinition.
class A {B *b;}       
#endif
class B {A *a;}       
#endif

没问题。

更新:将标题包含到守卫中(对不起,这是一个错误)。

当你只考虑"声明"时,你错过了一半的故事。c++还有"类定义"的概念,这是第三种新的动物——它既是定义(类的)又是声明(成员函数的)。

由于类不能被定义不止一次(就像任何定义一样),所以不能在中包含不止一次类定义的头文件。

现在假设您在foo.hpp中有一些实用程序类Foo,并且您有两个独立的模块a.hppb.hpp,它们都需要Foo。你的主程序必须包括a.hppb.hpp,但现在你试图包括foo.hpp,因此Foo的类定义,两次。

因为它允许您多次#include头而不用担心冲突。

如果你有一层嵌套,这是不必要的,如果你有几个嵌套,这是必不可少的(考虑包括h1,然后包括h2,其中包括h1,因为它需要它)。

如果是严格意义上的头符保护,则不需要这样做——如果包含multiply,则声明已经可见。

不这样做的另一个原因是,在严格的头文件保护之外的声明可能会禁用编译器对多个包含的头文件的优化(也就是说,它会打开头文件更多次)。

您的系统不能防止循环包含。例如:

标题:

#include "B.h"
#ifndef A_H_INCLUDED
#define A_H_INCLUDED
// ...
#endif // A_H_INCLUDED

头B:

#include "A.h"
#ifndef B_H_INCLUDED
#define B_H_INCLUDED
// ...
#endif // B_H_INCLUDED
源文件:

#include "A.h" // includes B, which includes A, which includes B, ...

一个简单的答案就是编译速度。编译器(如GCC和其他编译器)可以检测到完整的文件头保护,并在多次遇到这些文件时避免读取和重新处理这些文件。如果您没有将整个文件包装在头保护中,那么您将很有可能迫使编译器在每次遇到头时重新计算头。