反对反对使用了ifndef就不需要pragma once的说法。

现代编译器(甚至包括较新的 MSVC)能够识别作为 include guard 的 ifndef ,在发现 ifndef 覆盖了文件中的所有 token 时避免重复读取文件。因此在现代编译器上 ifndef 本身的编译速度就足够快,不需要 pragma once。


我自己不喜欢#pragma once,因为它不是 100% 可靠,防止的是重复引入相同文件,而不是防止重复引入相同内容。

自己写一般不会傻到搞出这种问题,但如果依赖了其它库,控制权就不在你手里了。很可能你所依赖的库会导致你的工程里存在两份另一个库的头文件。

比如你可能会同时依赖 fmtlib 和 spdlog,而 spdlog 又自带一份 fmtlib。你的某处代码可能在直接 include 了 fmtlib 的 format.h 的同时,又因为使用 spdlog 而间接 include 了 spdlog 里的那份 format.h。如果 format.h 里用的是 pragma once,就直接重定义了……


vc6 你添加的class头文件就会自动加上ifndef和pragma once,如 @SuperSodaSea 说得一样,的确是为了兼容性。后来的ide就直接抛弃ifndef了,毕竟分析它也是要耗时间的。gcc也有对ifndef的优化。实际上,编译器对头文件的处理是有优化的,并不是你include了就会实实在在的走系统调用open一次,所以从性能上来说,应该没啥差别。pragma once还有一个好处,就是不用担心ifndef重名的问题


反对使用了ifndef就不需要pragma once的说法。现在主流的编译器都有对progma once的支持,并可由此提高编译速度(使用pragma once的编译速度比ifndef的写法快),加上ifndef是为了确保兼容性,可以在支持和不支持pragma once的编译器。因此两种都使用可以在提高编译速度的前提下保证兼容性。

可以不同时使用的。

我一般就是使用其中一个。不会同时使用两个。

以前#pragma once很多编译器不支持,但是即便不支持,其本身并不会引起错误,所以对于跨平台代码就两个一起用咯,最安全。

现在已经没有太大必要了。


同时使用二者是比较好的实践。#ifndef是完全符合标准的,所有编译器都能正确理解它并完成你的意图。但是只用#ifndef的话,缺点不单单是需要多写两行代码和想出一个合适的#define名字,更大的缺点是它要求编译器一直扫描到文件尾,找到对应的#endif,才能确定你的意图是整个头文件都要略过。然而这时候已经花费了许多CPU时间(和电能),前面做的那么多都是白忙。

#pragma once不是标准,通用性不那么强,但是即使不支持的编译器也仅仅忽略它,不会导致错误,这是标准的规定,遇到不认识的#pragma必须忽略。它的优点是只要出现并被编译器识别出来,编译器就直接放弃对整个头文件的分析,当这个头文件是空文件,能提高编译效率。所以,应该把#pragma once视为非标但经常有用(偶尔无效)且不会坏事的加速器,把它跟标准的方法一起用,这样能达到最佳效果。

最后说一句,#pragma once应该放在头文件的第一行,不要包在#ifndef/#endif里面。为啥?这是一个留给初学者的简单的思考题。

//---------- 补充一点

有人说应该用_Pragma代替#pragma。这是对C99和C++11标准的误解。_Pragma只是比#pragma更灵活,针对once,它们同样是非标的。


#pragma once是微软的最早开始用的,并不是所有编译器都支持,不过现在的主流编译器一般都支持支持的编译器可以参考如下链接pragma once它的好处是,它一般写在第一行,支持的编译器预处理期间可以很快扫描并识别,效率上略高一点。#ifndef格式的,基本上通用于所有编译器,兼容性最好。但是,如果多次包含同一个文件的预处理效率略低因此,就出现了同时使用两种的情况,兼顾效率和兼容性。

因为pragma once不标准,怕有的编译器不支持,所以还用了ifndef。

要我说,用pragma once就够了,不用怕。

现代编译器没有不支持的。

ifndef写起来太麻烦,不要忘了文件尾还有一个undef。而且我习惯改名,不管是代码里面的名字,还是文件名,经常改。ifndef要与文件名匹配,不然看起来太不舒服了,这样就很麻烦。还是pragma once方便。


#pragma once是属于广泛应用却没有被纳入标准的东西, 个人认为是极其好用的, 因为用#ifndef需要考虑命名问题, 如果是多个文件层次的编译结构, 容易撞车. (num/img和resource/img).

所以如果你在PC上开发, 妥妥用#pragma once吧. 然后实在是不兼容, 再用脚本统一处理头文件添加#ifndef.

几年前还小心翼翼的两个都用,现在只用#pragma once。背景:c++主流跨平台开发,pc,mobile和一些嵌入式系统。至今没遇到不支持的编译器,如果真遇到不支持的,宁愿生成代码或想办法换编译器,如一些arm平台可以自己折腾编译器。


以下是以前写的一点材料,这里贴一下,希望有所帮助。


我写的issue原文:

标题:Consider changing include guards to pragma once.

We can use the _Pragma directive for header file guard.

example:

#ifndef FOO_BAR_BAZ_H_
#define FOO_BAR_BAZ_H_
...
#endif // FOO_BAR_BAZ_H_

change to

_Pragma("once")

There are several factors to support the lowcase file/directory name.

portability Cross-platform problems will not occur, compiler implement standard, they will support it. readability Generally, if you just want to do some guard, you should not care about the name and the directory of this file. and the naming of the guard is yet another big question, nonstandard things always end up in a fight. convenience just copy paste for the _Pragma("once"), here and there.

Computer industry set the standards, so does our company.

feel free to refer to tencent cplusplus code standard, 「2.2.【必须】 头文件保护」: https://git.code.oa.com/standards/cpp#22%E5%BF%85%E9%A1%BB-%E5%A4%B4%E6%96%87%E4%BB%B6%E4%BF%9D%E6%8A%A4

More importantly, the _Pragma directive is the standard level in C++, instead of the a specific compiler extension like GCCs # pragma, which maximizes stability and compatibility.

see:


我写的wiki原文(工程标准 -&> C++ Coding Guideline):

标题:The _Pragma("once") Guard

_Pragma操作符

在C/C++标准中,#pragma是一条预处理的指令(preprocessor directive)。简单地说,#pragma是用来向编译器传达语言标准以外的一些信息。举个简单的例子,如果我们在代码的头文件中定义了以下语句:

#pragma once

要达到与上例#pragma类似的效果,则只需要如下代码即可。

_Pragma("once");

而相比预处理指令#pragma,由于_Pragma是一个操作符,因此可以嵌套在宏中。我们可以看看下面这个例子:

#define CONCAT(x) PRAGMA(concat on #x)
#define PRAGMA(x) _Pragma(#x)
CONCAT( ..concat.dir )

这里,CONCAT( ..concat.dir )最终会产生_Pragma(concat on "..concat.dir")这样的效果。而#pragma则不能在宏中展开,因此从灵活性上来讲,标准的 _Pragma具有更大的灵活性

注意,为什么强调「标准」,我指的是

_Pragma("once")

而不是

#pragma once

以上,可以看出,不管从可移植性、可读性、便利性,还是规范角度看,_Pragma("once")都有优势。使用新标准可以提高开发效率,用最自然的思维方式、最简单的代码,表达大量内容。

因为前者是语言层面的标准,而后者是编译器的扩展,可能在标准通过_Pragma操作符之前,GCC就已经实现了#pragma,这个时候,windows,macOS系统的编译器可能是不认识#pragma的,他可能是别的foo, bar。但是,标准通过之后,所有编译器都将遵循标准。

所以,使用标准,会是最通用的方式,也是我们的方式。

详见n3690.pdf:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3690.pdf


不过有观点认为,我们不该使用_Pragma("once"),因为编译器的实现有bug。(https://stackoverflow.com/questions/1143936/pragma-once-vs-include-guards)

我们确立一个标准,提高开发效率的同时,也要确认该标准是否稳定可用,为了防止引入未知的问题。

网上的资料不多,就直接看了下内核、编译器的实现。

早期存在的问题在于,比如foo.h -&> /path/to/foo.h链接会共享相同的inode,导致编译器无法区分。(详见附录「内核实现分析」)

在问题相关的上下游,有如下若干事实: 编译器层面,gcc在3.4.0版本的时候已经修复了上述问题。(详见附录「编译器实现分析」) 开发者层面,绝大多数项目都不会存在上述问题。

这让我们相信标准,且相信标准的实现。

极端假设,即使编译器仍存在这个所谓的bug,并且我们刻意触发了,那么会不会有严重问题呢?答案是否定的。因为,它和ifndef造成的macro冲突问题是同一级别的,而且更容易在最早期的编译阶段被发现。换言之,既然你都可以接受ifndef,那么为什么不能接受_Pragma("once")呢,后者即便是在最邪恶的情况下,也不会比前者错得更多。

结论:我们可以使用标准的_Pragma操作符

大家已经将这个结论,作为我们的共识。在代码规范中已更新。

具体的编码,可以直接遵循规范:

如果存在ifndef头文件保护,那么麻烦进行如下替换:

#ifndef FOO_BAR_BAZ_H_
#define FOO_BAR_BAZ_H_
...
#endif // FOO_BAR_BAZ_H_

改成

_Pragma("once")

如果不存在,请直接使用

_Pragma("once")


内核实现分析(linux-5.7.0):

编译器实现分析(gcc-9.2.0):


可以关注公众号「开发者技术精选」,获取更多优质内容。

这里精选计算机科学技术,帮助程序员成长。 发文内容有技术:数据结构、演算法、编程语言、操作系统、资料库、存储、计算机体系结构等。 有思想:思考成果、哲学、逻辑、读书等; 频率较低,当前每月三篇,宁缺毋滥、以质量先行。


标准中没有规定,剩下的就是个人喜好,毕竟代码最终都由个体写出来的。代码可能在多种编译器下被编译。


pragma once 是按编译器偏好实现的,兼容性不一定有保证。比如 gcc 至少在 unix 下就很粗暴地只比较了文件的 md5 和 last modified time


once是微软最初用的,后被gcc兼容,并未纳入标准,早期gcc会弹warning但依然兼容。

ifdef写法更符合规范,_Pragma由c99引入,当然单独讨论once的话,我建议ifdef写法。


cpp真是个垃圾,猴年马月还得考虑这些问题


每个人都需要安全感
推荐阅读:
相关文章