为什么在 c++ 中包含守卫不是默认设置

Why aren't include guards in c++ the default?

本文关键字:默认 设置 守卫 包含 c++ 为什么      更新时间:2023-10-16

我基本上在我的c ++项目中的每个头文件中都使用#pragma once(或者你使用包括#ifndef...的警卫(。这是巧合还是您在大多数开源项目中发现的(以避免依赖个人项目经验的答案(。如果是这样,为什么不反过来:如果我希望多次包含头文件,我会使用一些特殊的预处理器命令,如果没有,我会保持文件原样。

C++编译器的行为是根据它如何处理每个翻译单元来指定的。翻译单元是预处理器运行后的单个文件。事实上,我们有一个约定,即在某些文件中收集声明并称它们为"头"文件,这对编译器或C++标准没有任何意义。

简而言之,该标准不提供"头文件",因此它无法提供自动包含保护头文件的功能。该标准仅规定了预处理器指令#include其余的只是约定。没有什么能阻止你向前声明所有内容而不使用头文件(除了可惜任何应该维护该代码的人......

所以头文件

并不特殊,没有办法说"这是一个头文件,保护它",但为什么我们不能保护所有被#include的东西呢?因为#include比其他语言的模块系统更强大,也更不强大。 #include会导致预处理器粘贴到其他文件中,而不一定是头文件。有时,如果您在不同文件的一堆不同命名空间中具有相同的 using 和 typedef 声明,这会很方便。您可以将它们收集在一个文件中,并在几个地方#include它们。您不希望自动包含防护装置阻止您这样做。

使用 #ifndef#define 有条件地包含标头也只是约定俗成。该标准没有"包括警卫"的概念。(然而,现代编译器实际上知道包含保护。识别包含保护可以允许更快的编译,但它与正确实现标准无关。

变得迂腐

该标准确实大量使用"标头"一词,特别是在引用 C 和 C++ 标准库时。但是,#include的行为是在 § 16.2 *Source file* inclusion (emph. mine( 下定义的,它不授予头文件任何特殊权限。

正在努力将适当的模块系统纳入C++标准。

因为歇斯底里的葡萄干。

C++预处理器与 40 多年前设计的 C 预处理器几乎相同。那时的编译器要简单得多。预处理器甚至更简单,只是一个哑宏处理器,甚至不是编译器。 尽管 C++ 标准没有指定它如何用于标准标头,但从概念上讲#include仍然与 40+ 年前相同:它导致预处理器将命名文件的内容插入到包含文件中。

在一个简单的 1970 年代 C 代码库中,没有大量的相互依赖关系和子模块,可能不需要包含保护。早期的预标准 C 不使用函数原型,这是当今大多数包含文件使用的内容。如果包含两次标头导致错误,则可能会重新排列代码以防止它被包含两次。

随着代码库的增长和变得更加复杂,我认为意外包含两次标头(可能间接地,通过其他标头(的问题变得越来越普遍。一种解决方案是改变预处理器以使其更智能,但这需要每个人都使用新的预处理器,并且会使其更大更慢。因此,相反,开发了一种约定,它使用预处理器的现有功能(#define#ifndef(解决了这个问题,不是通过防止包含两次标头,而是简单地使包含标头两次无害,因为它在第一次包含后没有任何效果。

随着时间的推移,该约定被更广泛地使用,现在几乎是通用的,除了罕见的标头示例,这些标头被设计为包含两次并以这种方式正确工作(例如 <assert.h> (。

后来,#pragma once被一些编译器引入,作为一种替代的、不可移植的方式,具有与包含守卫相同的效果,但到那时,有数千个各种 C 预处理器的副本在这个词周围使用,包含守卫已经成为常态。

因此,目前的行为几乎可以肯定是由于历史原因。今天编写的现代语言,对于今天非常强大的计算机,如果从头开始设计,就不会使用像 C 预处理器这样的东西。但C并不是在21世纪设计的。我认为包含保护约定是随着时间的推移慢慢建立的,不需要对现有软件进行任何更改即可使其工作。现在更改它会破坏未知数量的 C 和C++代码,这些代码依赖于当前行为,并且可能不是一种选择,因为向后兼容性对 C 和 C++都很重要。

我必须同意主要因素是历史因素,也就是说,偶尔你会看到依赖于它们的代码不存在。MAME 就是一个例子:它通过多次包含它并定义不同的宏,从一个可读的基于宏的文件构建复杂的数据结构(或者至少是我上次看过,前段时间(。如果包含保护是自动的,您会遇到需要一种方法来关闭它们的代码。