我在我的c ++项目中的每个头文件中使用#pragma once
(或者你使用includeguardsàla#ifndef...
)。这是巧合还是你在大多数开源项目中找到的东西(为了避免只依赖 个人项目经验的答案)。如果是这样,为什么不相反:如果我想要多次包含头文件,我会使用一些特殊的预处理器命令,如果不是,我将文件保留原样。
答案 0 :(得分:7)
C ++编译器的行为是根据它如何处理每个转换单元来指定的。翻译单元是预处理器在其上运行后的单个文件。事实上,我们有一个在某些文件中收集声明并调用它们的约定" header"文件对编译器或C ++标准没有任何意义。
简单地说,标准没有提供"头文件"所以它不能提供自动包含防护头文件。该标准仅提供预处理器指令#include
,其余仅仅是约定。没有什么可以阻止你向前声明所有内容并且不使用头文件(除了怜悯任何人应该维护该代码......)。
所以标题文件不是特别的,并且没有办法说出#34;这是一个标题文件,保护它"但为什么我们不能保护所有内容得到#include
' d?因为#include
比其他语言的模块系统更强大。 #include
导致预处理器粘贴到其他文件中,而不一定是头文件。有时,如果在不同文件中的一堆不同命名空间中使用相同的using和typedef声明,这可能很方便。您可以在一个文件中收集它们,并在几个地方#include
收集它们。你不会想要自动包含警卫阻止你这样做。
使用#ifndef
和#define
有条件地包含标头也仅仅是惯例。该标准没有"包括警卫"的概念。 (然而现代编译器实际上都知道包括警卫。识别包括警卫可以允许更快的编译,但它与正确实施标准无关。)
迂腐
标准确实使用了" header"这个词,特别是在引用C和C ++标准库时。但是,#include
的行为是在§ 16.2 *Source file* inclusion
(emph.mine)下定义的,并且它不会对头文件赋予任何特殊权限。
努力将适当的模块系统纳入C ++标准。
答案 1 :(得分:7)
因为歇斯底里的葡萄干。
C ++预处理器几乎与40多年前设计的C预处理器完全相同。当时的编译器要简单得多。预处理器甚至更简单,只是一个愚蠢的宏处理器甚至不是编译器。虽然C ++标准没有规定标准头文件的工作方式,但概念上#include
仍然与40多年前相同:它使预处理器将命名文件的内容插入到包含文件中文件。
在一个简单的20世纪70年代的C代码库中,没有很多相互依赖关系和子模块,可能不需要包含保护。早期的标准前C没有使用函数原型,这是目前大多数包含文件的用法。如果包含两次标题导致错误,您可能会重新安排代码以防止它被包含两次。
随着代码库的增长和变得越来越复杂,我认为无意中包括两次标题(可能通过其他标题间接地)变得更加常见。一种解决方案可能是改变预处理器以使其更智能,但这需要每个人都使用新的预处理器,并且会使它变得越来越大。因此,开发了一个使用预处理器(#define
和#ifndef
)的现有功能解决问题的约定,而不是通过防止包含两次标头,而是简单地使其包含两次标题无害,因为它在第一次被包括后没有效果。
随着时间的推移,约定变得更加广泛使用,现在几乎是通用的,除了标题的罕见示例,它们被设计为包含两次并写入以正确的方式工作(例如<assert.h>
)。
后来,#pragma once
被一些编译器引入作为替代的,不可移植的方式,与包含警卫具有相同的效果,但到那时,周围使用了数千个各种C预处理器的副本。一句话,包括警卫已成为常态。
因此,目前的行为几乎可以肯定是由于历史原因。今天编写的现代语言,对于今天非常强大的计算机,如果从头开始设计,就不会使用类似C预处理器的东西。但是C并没有在21世纪设计出来。我认为包含守卫会议是随着时间的推移缓慢建立的,并且不需要对现有软件进行任何更改以使其工作。现在更改它会破坏依赖于当前行为的未知数量的C和C ++代码,并且可能不是一个选项,因为向后兼容性对于C和C ++都很重要。
答案 2 :(得分:0)
我必须同意主要因素是历史因素,有时候你会看到依赖它们的代码不在那里。 MAME就是一个例子:它通过多次包含不同的宏来构建复杂的数据结构(或者至少是我上次看过的,前一段时间)。如果包含警卫是自动的,那么您会遇到需要关闭它们的代码。