为什么要将整个标题内容放在保护令牌中? C和C++在声明之间定义了一个定义。
可以多次声明符号,但只允许定义一次。通过了解这一点,我有了一个想法,即将声明放在防护装置之外,而将定义放在防护装置内部:为什么要将整个标题内容放在保护令牌中? C和C++在声明之间定义了一个定义。,c++,c,header,header-files,include-guards,C++,C,Header,Header Files,Include Guards,可以多次声明符号,但只允许定义一次。通过了解这一点,我有了一个想法,即将声明放在防护装置之外,而将定义放在防护装置内部: // 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 "otherhea
// 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:
b、 h
当在b.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
没问题
更新:将标题包含放入保护中(很抱歉,这是一个错误)。如果严格来说它是一个标题保护,则没有必要-如果包含多个标题,则声明已经可见
反对这种做法的另一个原因是,在严格的头保护之外的声明可能会禁用编译器对多重包含头的优化(也就是说,它将多次打开头)。因为它允许您多次包含头,而不必担心冲突
虽然只有一个嵌套级别是不必要的,但如果有多个嵌套级别(考虑包括h1,然后包括h2,其中包括h1,因为它需要h1)。系统无法防止循环包含。例如: 标题A:
#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, ...
当你只考虑“声明”时,你错过了一半的故事。C++还具有“类定义”的概念,它是一种第三种新型动物——它既是类的定义,也是成员函数的声明。 由于类的定义不能超过一次(就像任何定义一样),所以不能超过一次包含带有类定义的头文件 现在假设您在
Foo.hpp
中有一些实用程序类Foo
,并且您有两个独立的模块a.hpp
和b.hpp
,它们都需要Foo
。您的主程序必须包含a.hpp
和b.hpp
,但现在您尝试两次包含foo.hpp
,从而包含foo
的类定义
输入includeguards.一个简单的答案就是编译速度。编译器,如GCC和其他可能的编译器,可以检测到完整的文件头保护,并避免在多次遇到这些文件时读取和重新处理这些文件。如果您不将整个文件包装在头保护中,那么很有可能每次遇到头时都会迫使编译器重新计算它。好吧,为什么不呢?为什么要投入精力去思考头球后卫能做什么,不能做什么?您的方法的好处在哪里?大多数教师和专业人士都不鼓励在单个文件中实现类。@harold:并为不支持它的编译器在其周围添加
\ifdefs
。@v01d我不是在头中实现类,只是定义成员方法名称等,当然,实现要用到cpp文件。这是真的,但是通过仔细设计可以避免循环包含。在一个完全分离的头/实现设计中,永远不会有真正的需要,甚至自然的情况下,循环包含应该发生。“我把定义放在警卫里面,而把声明放在警卫外面。”@Calmarius:听起来像是维护和可读性的噩梦。为什么要做这样一件费劲乏味的事情?为什么要在这上面浪费时间呢?你强迫自己做的决定越少,犯错误的机会就越少。它如何做到这一点而不首先扫描文件?@Calmarius第一次包含文件时,编译器会读取它并记录它是否有完整的文件include guard(或pragma one)然后知道它不必在翻译单元的其余部分再次阅读它。
#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, ...