为什么 C/C++'的“#pragma Once”不是ISO 标准?
我目前正在做一个大项目,维护所有这些包括警卫让我发疯!手写是令人沮丧的浪费时间。尽管许多编辑器可以生成包含保护,但这并没有多大帮助:
编辑器根据文件名生成保护符号。当不同目录中具有相同文件名的标头时,就会出现此问题。他们俩都会得到相同的护卫。将目录结构包含到保护符号中需要编辑器采取一些奇特的方法,因为宏中的斜杠和反斜杠不是最好的东西。
当我必须重命名一个文件时,我也应该重命名所有包含防护(在 ifndef、define 和理想情况下 endif 的注释中)。烦人。
预处理器充斥着大量的符号,但不知道它们的含义。
尽管定义被包含一次,编译器每次遇到头包含时仍然打开头。
包含防护不适合命名空间或模板。事实上,它们正在颠覆命名空间!
您的守卫符号有可能不是唯一的。
当程序在单个目录中包含少于 1000 个标头时,也许它们是可以接受的解决方案。但如今呢?它是古老的,与现代编码习惯无关。最让我困扰的是这个问题几乎可以通过 #pragma Once 指令完全解决。为什么它不是一个标准?
I am currently working on a big project and maintaining all those include guards makes me crazy! Writing it by hand is frustrating waste of time. Although many editors can generate include guards this doesn't help much:
Editor generates guard symbol based on a filename. The problem occurs when you have headers with the same filename in different directories. Both of them will get the same include guard. Including directory structure into the guard symbol would require some fancy approach from the editor, since slashes and backslashes in the macro are not the best thing.
When I have to rename a file I should rename all the include guards as well (in the ifndef, define and ideally endif's comment). Annoying.
Preprocessor is flooded with tons of symbols without a clue what they mean.
Nevertheless definition is included once, compiler still opens header every time it meets header inclusion.
Include guards don't fit into namespaces nor templates. In fact they are subverting namespaces!
You have a chance that your guard symbol won't be unique.
Maybe they were acceptable solution in times when programs contained less than 1000 headers in single directory. But nowadays? It is ancient, it has nothing to do with modern coding habits. What bothers me the most is that this issues could be almost compeletly solved by #pragma once directive. Why is it not a standard?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
像
#pragma Once
这样的指令以完全可移植的方式定义并不容易,而且具有明确的好处。它引发问题的一些概念并未在所有支持 C 的系统上得到很好的定义,并且以简单的方式定义它可能不会比传统的包含防护提供任何好处。当编译遇到
#pragma Once
时,它应该如何识别这个文件,以便它不会再次包含它的内容?显而易见的答案是文件在系统上的唯一位置。如果系统对所有文件都有唯一的位置,那么这很好,但许多系统提供的链接(符号链接和硬链接)意味着“文件”没有唯一的位置。仅仅因为通过不同的名称找到该文件就应该重新包含该文件吗?可能不会。
但现在有一个问题,如何才能以一种在所有平台上都具有确切含义的方式定义#pragma Once的行为——甚至那些甚至没有目录的平台,更不用说符号链接 - 并且仍然在拥有它们的系统上获得理想的行为?
您可以说文件标识是由其内容决定的,因此如果包含的文件具有
#pragma Once
并且包含的文件具有完全相同相同的内容,则第二个及后续的#include
无效。这很容易定义并且具有明确定义的语义。它还具有良好的属性,如果一个项目从支持和使用文件系统链接的系统移动到不支持和使用文件系统链接的系统,它的行为仍然相同。
缺点是,每次遇到包含
#pragma Once
的包含文件时,都必须使用迄今为止已包含的#pragma Once
对照所有其他文件检查其内容。这意味着在任何情况下都会对性能造成类似于使用#include 保护的影响,并且给编译器编写者增加了不小的负担。显然,其结果可以被缓存,但对于传统的包含防护来说也是如此。传统的包含防护强制程序员选择一个作为包含文件的唯一标识符的宏,但至少该行为是定义良好且易于实现的。
考虑到潜在的陷阱和成本,以及传统的包含防护确实有效的事实,标准委员会认为没有必要标准化
#pragma Once
,这对我来说并不奇怪。A directive like
#pragma once
is not trivial to define in a fully portable way that has clear an unambiguous benefits. Some of the concepts for which it raises questions are not well defined on all systems that supportC
, and defining it in a simple way might provide no benefit over conventional include guards.When the compile encounters
#pragma once
, how should it identify this file so that it doesn't include its contents again?The obvious answer is the unique location of the file on the system. This is fine if the system has unique locations for all files but many systems provide links (symlinks and hardlinks) that mean that a 'file' doesn't have a unique location. Should the file be re-included just because it was found via a different name? Probably not.
But now there is a problem, how is it possible to define the behaviour of
#pragma once
in a way that has an exact meaning on all platforms - even those that don't even have directories, let alone symlinks - and still get the desirable behaviour on systems that do have them?You could say that a files identity is determined by its contents, so if an included file has a
#pragma once
and a file is included that has exactly the same contents, then the second and subsequent#include
s shall have no effect.This is easy to define and has well defined semantics. It also has good properties such that if a project is moved from a system that supports and uses filesystem links to one that doesn't, it still behaves the same.
On the downside, every time an include file is encountered containing a
#pragma once
its contents must be checked against every other file using#pragma once
that has already been included so far. This implies a performance hit similar to using#include
guards in any case and adds a not insignificant burden to compiler writers. Obviously, the results of this could be cached, but the same is true for conventional include guards.Conventional include guards force the programmer to choose a macro that is the unique identifier for an include file, but at least the behaviour is well-defined and simple to implement.
Given the potential pitfalls and costs, and the fact the conventional include guards do work, it is not surprising to me that the standards committee didn't feel the need to standardize
#pragma once
.包含防护绝对是一个烦恼,C 最初的设计应该是默认情况下包含一次标头 - 需要一些特殊选项来多次包含标头。
然而,事实并非如此,而且你大多不得不使用包含防护。也就是说,
#pragma Once
得到了相当广泛的支持,因此您可能可以不用使用它。就我个人而言,我通过向包含防护添加 GUID 来解决您的第一个问题(类似于包含文件)。它很丑陋,而且大多数人讨厌它(所以我经常被迫在工作中不使用它),但你的经验表明这个想法有一定的价值 - 即使它丑陋得可怕(但话又说回来,整个包含守卫的事情是有点黑客 - 为什么不全力以赴?):
我听说编译器实际上并没有重新打开包含防护的头文件(他们已经学会了识别这个习惯用法)。我不确定这是否属实(或在多大程度上属实);我从来没有测量过。我也不担心这个问题,但我的项目并没有大到成为一个问题。
我的 GUID hack 几乎解决了第 1、5 和 6 项。我只接受第 2、3 和 4 项。实际上,对于第 2 项,当您重命名文件时,您可以在不重命名包含保护宏的情况下生存,因为 GUID 将确保它仍然是独一无二的。事实上,根本没有理由将文件名与 GUID 合并在一起。但我知道——我想是传统。
Include guards are definitely an annoyance, and C should have originally been designed such that headers would be included once by default - requiring some special option to include a header multiple times.
However, it wasn't ,and you're mostly stuck with having to use include guards. That said,
#pragma once
is pretty widely supported so you might be able to get away with using it.Personally, I solve your 1st problem (similarly named include files) by adding a GUID to the include guard. It's ugly, and most people hate it (so I'm often forced to not use it at work), but your experience shows that the idea has some value - even if it is hideously ugly (but then again the whole include guard thing is kind of a hack - why not go whole hog?):
I've heard that compilers don't actually reopen header files that have include guards (they've learned to recognize the idiom). I'm not sure if that's true (or to what extent it's true); I've never measured it. I also don't worry about it, but my projects aren't so huge that it's a problem.
My GUID hack pretty much solves items 1, 5, and 6. I just live with items 2, 3, and 4. Actually, for item 2 you could live without renaming the include guard macro when you rename the file as the GUID will ensure it remains unique. In fact, there's no reason to incorporate the filename at all with the GUID. But I do - tradition, I suppose.
命名空间和模板不应跨越标头。亲爱的我,不要告诉我你这样做:
你已经说过了。
namespaces and templates should not span headers. Dear me, don't tell me you do this:
You said that already.
正如已经指出的,C++ 标准应该考虑不同的开发平台,其中一些平台可能存在限制,导致 #pragma Once 支持无法实现。
另一方面,之前由于类似的原因没有添加对线程的支持,但较新的 C++ 标准仍然包含线程。在后一种情况下,我们可以针对非常有限的平台进行交叉编译,但开发是在成熟的平台上完成的。由于 GCC 支持此扩展,我认为,您问题的真正答案是没有任何人有兴趣将此功能推入 C++ 标准。
从实际角度来看,包含警卫给我们团队带来的麻烦比不遵守 #pragma Once 指令还要多。例如,如果文件重复并且后来包含两个副本,则包含防护中的 GUID 没有帮助。当仅使用 #pragma 时,一旦出现重复定义错误,我们就可以花时间统一源代码。但在包含保护的情况下,问题可能需要运行时测试才能捕获,例如,如果函数参数的默认参数副本不同,就会发生这种情况。
我避免使用包含警卫。如果我必须将代码移植到不支持 #pragma Once 的编译器,我将编写一个脚本,为所有头文件添加包含保护。
As was already noted, C++ Standard should account for different development platforms some of which may have limitations making #pragma once support impossible to implement.
On the other hand, support for threads was not added for the similar reason previously, but newer C++ Standard includes threads nevertheless. And in the latter case we may cross-compile for a very limited platform, but development is done on a full-fledged platform. Since GCC supports this extension, I think, the real answer to your question is that there is no interested party in pushing this feature into C++ Standard.
From practical standpoint, include guards caused our team more trouble than the incompliance of #pragma once directive. For instance, GUID in include guards do not help in case if file is duplicated and later both copies are included. When using only #pragma once we get duplicate definition error and can spend time unifying source code. But in case of include guards the problem may require run-time testing to catch, e.g. this happens if copies differ in default arguments for function parameters.
I avoid using include guards. If I have to port my code to a compiler without #pragma once support, I will write a script which will add include guards to all header files.
那么,您的项目中有两个名为
ice_cream_maker.h
的标头,它们都定义了一个名为ice_cream_maker
的类,该类执行相同的功能?或者您是否正在调用系统中的每个类foo
?编辑代码,以免多次包含标头。
对于依赖标头(而不是库的主标头),我经常使用标头防护,如下所示:
So you have two headers which are both called
ice_cream_maker.h
in your project, both of which have a class calledice_cream_maker
defined in them which performs the same function? Or are you calling every class in your systemfoo
?Edit the code so you don't include headers multiple times.
For dependent headers (rather than the main header for a library), I often use header guards which are like this:
IIRC,#pragma anything 不是语言的一部分。它在实践中经常出现。
(编辑:完全同意包含和链接系统应该更多地成为新标准的焦点,因为它是“当今时代”中最明显的弱点之一)
IIRC, #pragma anything is not part of the language. And it shows up in practice, a lot.
(edit:completely agree that the inclusion and linking system should have been more of a focus for the new standard as it is one of the most obvious weaknesses, in 'le this day and age' )
您可以通过将包含保护设置为包含文件中的类和命名空间的名称来避免名称冲突,而无需诉诸随机字符串。
此外,MS 编译器和 GCC 都支持 #pragma Once 已经有一段时间了,那么为什么你会因为它不在 ISO 标准上而烦恼呢?
You can probably avoid name collisions without resorting to random strings by setting the include guard to contain the name of the class and namespace that's in the file.
Besides, #pragma once is supported by both MS compilers and GCC for quite some time now so why does it bother you that it's not on the ISO standard?
一个务实的解决方案:
1)选择一些一致的保护命名策略(例如相对于项目根目录的路径+文件名,或者您选择的任何其他内容)。包括第三方代码可能的例外情况。
2)编写一个程序(一个简单的Python脚本就可以)来递归地遍历源树并验证守卫是否都符合策略。当守卫出错时,输出一个 diff(或 sed 脚本,或其他任何东西),用户可以轻松地应用它来修复。或者只是要求确认并从同一程序进行更改。
3)让项目中的每个人都使用它(例如,在提交到源代码管理之前)。
A pragmatic solution:
1) choose some consistent guard-naming policy (e.g. path relative to project root + file name, or whatever else you choose). Include possible exceptions for third-party code.
2) write a program (a simple python script would do) to walk recursively the source tree and verify that the guards all conformal to the policy. And whenever the guards are wrong, output a diff (or sed script, or whatever else) that the user can easily apply to fix. Or just ask for a confirmation and make changes from the same program.
3) make everyone on the project use it (say, before submitting to source control).
我认为正确的方法是允许仅使用特殊的编译指示进行多次包含,并且默认情况下不允许多次包含
例如:
既然你问了为什么。您是否已将提案发送给标准开发人员? :)
我也不送。
可以作为一个理由吗?
I think right way to allow to do multiple include with special pragma only and disallow to multiple include by default
for example:
So since you asked why. Did you send your proposal to standart developers? :)
I'm not send too.
Can one be a reason?