为什么C / C ++的“#pragma once”不是ISO标准?

我目前正在做一个大项目,并保持所有这些包括警卫让我疯狂! 用手写字是浪费时间。 尽pipe许多编辑可以产生包括守卫,但这并没有多大帮助:

  1. 编辑器根据文件名生成警卫符号。 当您在不同的目录中具有相同的文件名标题时,会出现问题。 他们两人将得到相同的包括后卫。 将目录结构包含在警卫符号中需要编辑器的一些奇特的方法,因为macros中的斜杠和反斜杠不是最好的。

  2. 当我不得不重新命名一个文件时,我应该重命名所有的包括守卫(在ifndef中,定义和理想的endif的评论)。 烦人。

  3. 预处理器充斥着大量的符号,而不知道它们的意思。

  4. 尽pipe如此,定义只包含一次,编译器每次遇到头文件时仍然会打开头文件。

  5. 包括守卫不适合命名空间或模板。 事实上,他们正在颠覆命名空间!

  6. 你有一个机会,你的警卫符号将不是唯一的。

在单个目录中的程序包含less于1000个标题的时候,它们可能是可以接受的解决scheme。 但是现在呢? 这是古老的,与现代编码习惯无关。 最让我困扰的是这个问题几乎可以通过#pragma一次指令来解决。 为什么它不是一个标准?

#pragma once这样的指令在一个完全便携的方式中定义并不是一件容易的事情,而且清晰明确的好处。 它提出问题的一些概念在支持C所有系统上都没有很好的定义,并且以一种简单的方式来定义它可能不会超过传统的包含守卫。

当编译遇到#pragma once ,应该如何识别这个文件,以便它不再包含它的内容?

显而易见的答案是该文件在系统上的唯一位置。 如果系统对所有文件都有唯一的位置,这很好,但是许多系统提供链接(符号链接和硬链接),这意味着“文件”没有唯一的位置。 该文件是否应该重新包含,只是因为它是通过不同的名称find的? 可能不会。

但是现在出现了一个问题,如何以一种在所有平台上具有确切含义的方式来定义#pragma once的行为,即使那些甚至没有目录,更不用说符号链接,并且仍然可以得到所需的在具有它们的系统上的行为?

你可以说文件的标识是由它的内容决定的,所以如果一个包含的文件有一个#pragma once并且包含的​​文件内容完全相同 ,那么第二个和后续的#include将不起作用。

这很容易定义和定义明确的语义。 它也有很好的属性,如果一个项目从一个支持和使用文件系统的链接转移到另一个没有的系统,它的行为也是一样的。

不利的一面是,每遇到一个包含#pragma once的包含文件,每次使用#pragma once检查其内容时,都必须检查其内容,而这个文件已经包含在内。 这意味着性能类似于使用#include警卫,并且给编译器编写者带来不小的负担。 显然,这样的结果可以被caching,但是传统的包括守卫也是如此。

传统的包含守卫强制程序员select一个macros,这是一个包含文件的唯一标识符,但至less该行为是定义明确且易于实现的。

考虑到潜在的缺陷和成本,以及传统的包括警卫的工作,我不觉得标准委员会没有必要将#pragma once标准化#pragma once

包括警卫肯定是一个烦恼,而C应该是最初被devise的,这样头默认包含一次 – 需要一些特殊的选项来包含头多次。

然而,事实并非如此,你大多坚持使用包括守卫。 也就是说, #pragma once被广泛的支持,所以你可以放弃使用它。

就我个人而言,我通过向包含守卫添加GUID来解决您的第一个问题(类似地命名为包含文件)。 这是丑陋的,大多数人都讨厌它(所以我经常被迫不在工作中使用它),但是你的经验表明,这个想法有一定的价值 – 即使它是丑陋的丑陋的(但是,一种黑客 – 为什么不去全猪?):

 #ifndef C_ASSERT_H_3803b949_b422_4377_8713_ce606f29d546 #define C_ASSERT_H_3803b949_b422_4377_8713_ce606f29d546 // blah blah blah... #endif 

我听说编译器实际上没有重新打开包含守卫的头文件(他们已经学会识别惯用法)。 我不确定这是否属实(或者在多大程度上是真的); 我从来没有测量过。 我也不担心,但是我的项目不是那么庞大,这是一个问题。

我的GUID几乎解决了项目1,5和6.我只是住在项目2,3和4.事实上,对于项目2,您可以在不重命名包含保护macros的情况下生存,因为GUID将确保重命名该文件它仍然是独特的。 实际上,没有理由将GUID与文件名结合起来。 但我确实 – 传统,我想。

  1. 你多久需要为这个项目添加一个包含文件? 将DIRNAME_FILENAME添加到警卫真的很难吗? 总是有GUID。
  2. 你真的重命名文件,经常? 自从? 另外,将GUARD放在#endif中同其他无用的评论一样烦人。
  3. 我怀疑你的1000头文件guard定义甚至是系统库生成的定义数量的一小部分(特别是在Windows上)。
  4. 我认为DOS(20多年前)的MSC 10跟踪了什么标题被包括在内,如果它们包含了警卫,如果再次包含它们,将会跳过它们。 这是旧技术。
  5. 名称空间和模板不应该跨越标题。 亲爱的我,不要告诉我你这样做:

     template <typename foo> class bar { #include "bar_impl.h" }; 
  6. 你已经说过了

正如已经指出的那样,C ++标准应该考虑到不同的开发平台,其中一些可能会有局限性,一旦支持不可能实现,就会使#pragma。

另一方面,由于以前类似的原因,没有添加对线程的支持,但更新的C ++标准包含线程。 而在后一种情况下,我们可以交叉编译一个非常有限的平台,但是发展是在一个完整的平台上完成的。 由于GCC支持这个扩展,我认为,对于你的问题真正的答案是没有兴趣的一方把这个function推到C ++标准中。

从实际的angular度来看,包括守卫让我们的团队比#pragma一次指令的使用更麻烦。 例如,如果文件被复制,并且随后两个副本都包含在内,那么在包含卫士中的GUID不起作用。 一旦我们得到重复的定义错误,并且可以花时间统一源代码,只使用#pragma。 但是在包含守卫的情况下,该问题可能需要运行时testing才能捕获,例如,如果副本在函数参数的默认参数中有所不同,则会发生这种情况。

我避免使用包括警卫。 如果我必须将我的代码移植到一个没有#pragma编译器的编译器,那么我将编写一个脚本,它将为所有的头文件添加包含守卫的脚本。

当您在不同的目录中具有相同的文件名标题时,会出现问题。

所以你有两个头文件,在你的项目中都被称为ice_cream_maker.h ,它们都有一个名为ice_cream_maker的类,它们在其中执行相同的function? 还是你打电话给你系统中的每个class级?

尽pipe如此,定义只包含一次,编译器每次遇到头文件时仍然会打开头文件。

编辑代码,以免多次包含标题。

对于依赖的头文件(而不是库的主头文件),我经常使用这样的头文件:

 #ifdef FOO_BAR_BAZ_H #error foo_bar_baz.h multiply included #else #define FOO_BAR_BAZ_H // header body #endif 

IIRC,#pragma 什么都不是语言的一部分。 它在实践中显示了很多。

(编辑:完全同意,包含和链接系统应该更多地是新标准的焦点,因为这是当今时代最明显的弱点之一)

通过设置include guard来包含文件中的类和名称空间的名称,您可以避免名称冲突,而不诉诸随机string。

另外,#pragma曾经被MS编译器和GCC支持一段时间了,为什么它会打扰你不符合ISO标准呢?

务实的解决scheme:

1)select一些一致的防护命名策略(例如,相对于项目根目录+文件名的path,或其他您select的其他内容)。 包含第三方代码的可能例外。

2)编写一个程序(一个简单的Python脚本会做)recursion地走源码树,并validation守卫都符合政策。 每当警卫出错,输出一个diff(或sed脚本,或其他任何),用户可以很容易地申请修复。 或者只是要求确认,并从相同的程序进行更改。

3)使项目中的每个人都使用它(比如在提交源代码控制之前)。

这不是C(而不是C ++),因为自1989年以来的更新都是由委员会。 成员有不同的目标,但没有人会为标准化每个文件只保存两行的function进行竞选。 如果你想要高雅,select一种从一开始就为它devise的现代语言; C旨在最大限度地减less编译器的复杂性

我认为正确的做法是允许多个包含特殊的杂注而不允许多个包含在默认情况下,例如:

 #pragma allow_multiple_include_this_file 

所以,既然你问为什么。 你把你的提案发给了标准开发者吗? :)我也不发送。 能成为一个理由吗?

Interesting Posts