关于包含带有保护的头文件

about include header file with guards

本文关键字:文件 保护 于包含      更新时间:2023-10-16

我正在读一本关于Applied C++的书。

包含保护将阻止头文件被更多地包含在源文件的编译过程中不止一次。您的符号名称应该是唯一的,我们建议根据名称选择名称文件的。例如,我们的文件cache.h包含以下内容警卫

#ifndef _cache_h_
 #define _cache_h_
 ...
 #endif // _cache_h_

Lakos描述了使用冗余include防护来加快速度汇编参见[Lakos96]。对于大型项目,打开需要时间每个文件,却发现include保护符号已经存在已定义(即文件已包含)。对汇编时间可能是戏剧性的,Lakos显示了可能的20倍当只有标准包含保护时,编译时间增加习惯于

[Lakos96]:大型C++软件设计。

我没有Lakos96参考书来参考这个概念,所以在这里寻求帮助。

我对以上文本的问题是

  1. 作者所说的"对于大型项目,打开每个文件都需要一些时间,却发现包含保护符号已经定义好了"是什么意思?

  2. 作者所说的"当使用标准包含防护装置时"是什么意思?

谢谢你的时间和帮助。

来自C++编码标准(Sutter,Alexandrescu)

许多现代C++编译器自动识别头保护(请参阅项目24),甚至不要打开同一个标题两次。有些还提供预编译的头,这有助于确保经常使用,很少更改的标头通常不会被解析为

所以,我认为这些建议已经过时了(除非你还在使用一些过时的编译器)。

关于您的问题:

  1. 这意味着:打开一个不需要的文件(因为它已经被包含;你会知道,因为已经定义了包含保护)是昂贵的;如果你做了很多次,这可能会成为一个问题(如果你的项目中有数百个文件,就会发生这种情况)
  2. 与使用非冗余编译保护相反

什么是冗余编译保护?

天真的编译器会在每次包含该文件时重新加载该文件。到避免这种情况,将RedundantIncludeGuards放在include:header.h 周围

 #ifndef HEADER_H_
  #define HEADER_H_
  // declarations
  #endif

foo.c

 #ifndef HEADER_H_
  #include "header.h"
  #endif

点击此处阅读更多信息。您的参考文献声称,通过这样做,您在编译过程中可以比foo.c只进行快20%

 #include "header.h"

我不知道Lakos96说了什么,但我还是要猜测。。。

一个标准的包含防护类似于:

foo.h

#ifndef FOO_H_INCLUDED
#define FOO_H_INCLUDED
....
#endif

冗余包含保护程序在包含文件时使用宏:

bar.c

#ifndef FOO_H_INCLUDED 
#include "foo.h"
#endif

这样,第二次包含foo.h文件时,编译器甚至不会在磁盘中搜索它。因此加速:想象一个大型项目,一个编译单元可能包含foo.h 100次,但只有第一个会被解析。其他99次它将被搜索、打开、标记化、由预编译器丢弃并关闭。

但请注意,那是在1996年。今天,GCC,举一个众所周知的例子,有一些特定的优化,可以识别包含保护模式,并使冗余的包含保护,嗯。。。,冗余的

Lakos的书很旧。这可能曾经是真的,但你应该在你的机器上计时。现在许多人不同意他的观点。http://www.allankelly.net/static/writing/overload/IncludeFiles/AnExchangeWithHerbSutter.pdf或http://c2.com/cgi/wiki?RedundantIncludeGuards或http://gamearchitect.net/Articles/ExperimentsWithIncludes.html

Herb Sutter,C++大师,现任ISO C++标准主席委员会反对外部包括警卫:

"顺便说一句,我强烈反对拉科斯的外部警卫基于两个理由:

  1. 大多数编译器没有任何好处。我承认我没有像拉科斯当时那样做过测量,但就我而言知道现在的编译器已经有了避免构建时间的智慧重读开销——甚至MSVC也会进行这种优化(尽管需要说"#pragma once"),而它是很多方面。

  2. 外部include保护违反了封装,因为它们要求许多/所有调用方了解标头的内部信息——在特别是用作保护的特殊#define名称。他们也是脆弱——如果你把名字弄错了怎么办?如果名字变了怎么办?"

我认为它指的是在头文件之外复制include保护,例如

#ifndef _cache_h_
#include <cache.h>
#endif

但是,如果您这样做,您将不得不考虑文件中的头保护有时会发生变化。在现代系统中,你肯定不会看到20倍的改进——除非你的所有文件都可能在一个非常远程的网络驱动器上——但通过将项目文件复制到本地驱动器,你会有更好的改进!

不久前也有一个类似的问题,关于"包含冗余文件"(指多次包含头文件),我构建了一个包含30个源文件的小型系统,其中包含"不必要"的<iostream>,包含和不包含<iostream>在编译时间上的总体差异为0.3%。我相信这一发现表明了GCC的改进,它"自动识别除了包含保护之外什么都不产生的文件"。

在一个大型项目中,可能有许多头文件,可能有100个甚至1000个。在正常情况下,每个头中都有include保护,编译器必须检查(但请参见下文)文件的内容,看看它是否已经包含在内。

收割台内部的这些防护装置是"标准"的。

Lakos建议(对于大型项目)在#include指令周围设置防护措施,这意味着如果标头已经包含,则甚至不需要打开它。

然而,据我所知,所有现代C++编译器都支持#pragma once指令,该指令加上预编译的头意味着在大多数情况下,问题不再是问题。

  1. 例如,在人员较多的大型项目中,可能有一个模块处理时间转换,作者可以选择使用TIME作为保护。然后你会有另一个,处理精确的时间,它的作者,不知道第一个,可能也会选择TIME。现在你有冲突了。如果他们使用TIME_TRANSFORMATIONPRECISE_TIMING_MODULE,他们就可以

  2. 不知道。我想这可能意味着"当你每次都这样做时,它会一直成为你的编码标准"。