6

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

包含守卫将防止在源文件编译期间多次包含头文件。您的符号名称应该是唯一的,我们建议根据文件名选择名称。例如,我们的文件 cache.h 包含这个包含保护。

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

Lakos 描述了使用冗余包含守卫来加速编译。见 [Lakos96]。对于大型项目,打开每个文件需要时间,才发现已经定义了包含保护符号(即文件已经被包含)。对编译时间的影响可能是巨大的,Lakos 表明,当仅使用标准包含保护时,编译时间可能会增加 20 倍。

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

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

我对上述文字的问题是

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

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

感谢您的时间和帮助。

4

6 回答 6

8

来自 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"
于 2013-07-23T08:56:09.453 回答
7

我不知道 Lakos96 说什么,但我还是猜猜……

一个标准的包含守卫是这样的:

foo.h

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

包含文件时,多余的包含保护正在使用宏:

酒吧.c

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

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

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

于 2013-07-23T08:58:19.563 回答
6

拉科斯的书很旧。这可能曾经是真的,但你应该在你的机器上计时。现在很多人不同意他的观点,例如 http://www.allankelly.net/static/writing/overload/IncludeFiles/AnExchangeWithHerbSutter.pdfhttp://c2.com/cgi/wiki?RedundantIncludeGuardshttp://gamearchitect。净/文章/ExperimentsWithIncludes.html

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

“顺便说一句,我强烈反对拉科斯的外援,有两个理由:

  1. 大多数编译器没有任何好处。我承认我没有做过测量,就像当时 Lakos 所做的那样,但据我所知,今天的编译器已经很聪明地避免了构建时间重读开销——即使是 MSVC 也做了这个优化(尽管它需要你说“#pragma once”),它在很多方面都是最弱的编译器。

  2. 外部包含守卫违反封装,因为它们需要许多/所有调用者了解标头的内部 - 特别是用作守卫的特殊 #define 名称。它们也很脆弱——如果你把名字弄错了怎么办?如果名字变了怎么办?”

于 2013-07-23T08:59:41.377 回答
1

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

头球内的这些后卫是“标准的”。

Lakos 建议(对于大型项目)将守卫放在#include指令周围,这意味着如果已经包含标题,则甚至不需要打开它。

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

于 2013-07-23T08:58:22.613 回答
1

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

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

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

前段时间有一个类似的问题,关于“包含冗余文件”(指多次包含头文件),我构建了一个包含 30 个源文件的小型系统,其中包含<iostream>“不必要的”,编译时间的总体差异是包括和不包括之间的 0.3% <iostream>。我相信这一发现显示了 GCC 的改进,即“自动识别除了包含保护之外不产生任何内容的文件”。

于 2013-07-23T08:58:51.233 回答
0
  1. 例如,在人员较多的大型项目中,可能会有一个模块处理时间转换,而它的作者可以选择将其TIME用作守卫。然后你会有另一个,处理精确的时间,它的作者,不知道第一个,也可以选择TIME。现在你有冲突。如果他们使用TIME_TRANSFORMATIONand PRECISE_TIMING_MODULE,他们会没事的

  2. 不知道。我猜这可能意味着“当你每次都这样做时,它始终如一地成为你的编码标准”。

于 2013-07-23T09:02:13.093 回答