#pragma曾经安全包括后卫吗?

Rya*_*rle 283 c++ include-guards

我已经读过使用时会有一些编译器优化,#pragma once这会导致更快的编译.我认为这是非标准的,因此可能造成跨平台兼容性问题.

这是非Windows平台(gcc)上大多数现代编译器支持的东西吗?

我想避免平台编译问题,但也想避免后备警卫的额外工作:

#pragma once
#ifndef HEADER_H
#define HEADER_H

...

#endif // HEADER_H
Run Code Online (Sandbox Code Playgroud)

我应该担心吗?我是否应该在这方面进一步消耗精力?

Mot*_*tti 314

#pragma once 确实有一个缺点(除非是非标准的),如果你在不同的位置有相同的文件(我们有这个因为我们的构建系统复制文件),那么编译器会认为这些是不同的文件.

  • 没有说服力......将构建系统更改为不复制文件但使用符号链接的系统,或仅在每个翻译单元中的一个位置包含相同的文件.听起来更像是你的基础设施是一个必须重组的混乱. (103认同)
  • 你也可以拥有两个或多个带有相同#define WHATEVER的文件,这会导致无趣的结束,这就是我赞成使用pragma一次的原因. (66认同)
  • 但是你也可以在不同的位置有两个同名的文件,而不必费心去创建不同的#define NAMES,这是以HEADERFILENAME_H的形式出现的 (35认同)
  • 如果在不同的目录中有不同的同名文件,#ifdef方法会认为它们是同一个文件.因此,存在一个缺点,另一个存在缺点. (3认同)
  • @rxantos,如果文件不同,`#ifdef`宏值也可以不同. (2认同)
  • @rxantos大多数都不是问题.我自己包含的许多项目使用**SUBSYSTEM_HEADERNAME_H**,而不仅仅是**HEADERNAME_H**(例如,**COMMON_CONTAINERS_GRID_H**或**ENGINE_LOGIC_GAMESTATE_H**(在我的多重二进制项目中都遵循**Binary_Subsystem_File**) )).在同一个子系统*中有两个相同的文件名*发生的可能性要小得多,如果确实发生了,那么很可能有人写了多余的东西,而你*希望*知道它. (2认同)

Zif*_*fre 171

使用#pragma once应该适用于任何现代编译器,但我认为没有任何理由不使用标准#ifndef包含保护.它工作得很好.一个警告是GCC #pragma once版本3.4之前不支持.

我还发现,至少在GCC上,它认识到标准#ifndef包括后卫并对其进行优化,所以它不应该慢得多#pragma once.

  • 它不是那样实现的.相反,如果文件第一次以#ifndef开头并以#endif结尾,gcc会记住它并始终跳过包含在未来的内容,甚至无需打开文件. (52认同)
  • 要使用包含保护,还需要定义一个新符号,例如`#ifndef FOO_BAR_H`,通常用于诸如"foo_bar.h"之类的文件.如果您稍后重命名此文件,是否应该相应地调整包含保护以符合此约定?此外,如果在代码树中的两个不同位置有两个不同的foo_bar.h,则必须考虑每个符号的两个不同符号.简短的回答是使用`#pragma once`,如果你真的需要在不支持它的环境中编译,那么继续为该环境添加包含保护. (31认同)
  • GCC关于守卫宏优化的文档:http://gcc.gnu.org/onlinedocs/cppinternals/Guard-Macros.html (13认同)
  • `#pragma once`通常更快,因为文件没有被预处理.`ifndef/define/endif`无论如何都需要预处理,因为在这个块之后你可以有一些可编译的东西(理论上) (9认同)
  • 它根本不应该慢(无论如何都是GCC). (8认同)
  • 名称冲突怎么样? (6认同)
  • 是的,它不比"#pragma once""慢得多".它是完全相同的.唯一的好处是避免使用`stat`系统调用,这可以在C++编译期间加起来. (4认同)
  • 使用#ifndef的旧版Guard引发了**#pragma一旦存在**所不存在的问题,我们还必须键入匹配的#endif。如果我们在同一头文件中也使用其他#ifdef,则可能会导致一些麻烦。如果忘记了任何#endif,错误将显示在错误的文件中,并且可能很烦人。另一个麻烦是,如果开发人员错误地为两个文件键入了相同的保护(当然,开发人员不可能做到这一点是完美的)。在某些设置中,该代码甚至可以在一段时间内保持不变(如果没有编译单元同时使用两个标头)。一次#pragma不会造成此类麻烦 (3认同)
  • Solaris编译器还实现了保护宏优化:["许多编译器,包括Sun Studio 11,将识别典型的包含保护,并且永远不会再次打开文件."](http://www.oracle.com/technetwork/服务器的存储/ Solaris10的/内容142295.html) (2认同)
  • 请注意:如果您使用的是 *VS* 或 *Eclipse* 等 IDE,**编译器并不是唯一需要担心的事情**。我发现“#define”防护可能会过多填充列表,并且在非常大的项目中搜索符号时,这些可能会有点烦人。 (2认同)
  • 如果有两个同名文件,#ifndef include 防护将会失败,这在库中经常发生。如果库 A 有“array.h”而 B 有“array.h”,则两者都可能将 ARRAY_H 作为包含保护,因此其中之一将被意外排除。 (2认同)

Mic*_*urr 58

我希望#pragma once(或类似的东西)符合标准.包括警卫不是一个真正的大问题(但他们似乎有点难以向学习该语言的人解释),但这似乎是一个可以避免的轻微烦恼.

实际上,由于99.98%的时间,#pragma once行为是期望的行为,如果防止多个包含头被编译器自动处理,带有#pragma或允许双重包含的内容将会很好.

但是我们拥有的东西(除了你可能没有#pragma once).

  • 我真正想要的是标准的`#import`指令. (47认同)
  • 一个标准的导入指令即将出现:http://isocpp.org/blog/2012/11/modules-update-on-work-in-progress-doug-gregor但还没有.我强烈支持它. (10认同)
  • @AHelps Vaporware.已经差不多五年了.也许在2023年你会回到这个评论并说"我告诉你了". (6认同)
  • ...并已将其纳入 C++20。 (3认同)
  • 即使它是在 C++20 中,它仍然可能是蒸气软件 https://vector-of-bool.github.io/2019/01/27/modules-doa.html (2认同)

Jar*_*Par 34

我不知道任何性能优势,但它确实有效.我在所有的C++项目中使用它(授予我使用MS编译器).我发现它比使用更有效

#ifndef HEADERNAME_H
#define HEADERNAME_H
...
#endif
Run Code Online (Sandbox Code Playgroud)

它执行相同的工作,并且不会使用其他宏填充预处理器.

GCC #pragma once正式支持3.4版本.


uce*_*ern 22

#pragma once自3.4以来GCC支持,请参阅http://en.wikipedia.org/wiki/Pragma_once以获得进一步的编译器支持.

我看到使用#pragma once而不是包含警卫的巨大好处是避免复制/粘贴错误.

让我们面对现实:我们大多数人几乎不能从头开始新的头文件,而只是复制现有的头文件并根据我们的需要进行修改.使用#pragma once而不是包含保护来创建工作模板要容易得多.我修改模板的次数越少,我就越不容易遇到错误.在不同的文件中包含相同的防护导致奇怪的编译器错误,并且需要一些时间来弄清楚出了什么问题.

TL; DR:#pragma once更容易使用.


Jon*_*ler 11

使用' #pragma once'可能没有任何影响(它在任何地方都不受支持 - 虽然它得到越来越广泛的支持),所以你需要使用条件编译代码,在这种情况下,为什么要打扰' #pragma once'?无论如何,编译器可能会优化它.但它确实依赖于您的目标平台.如果你的所有目标都支持它,那么继续使用它 - 但它应该是一个有意识的决定,因为如果你只使用pragma然后移植到不支持它的编译器,那么所有地狱都会破裂.


Edw*_*vis 11

我使用它并且我很满意它,因为我必须输入更少的东西来制作新的标题.它在三个平台上运行良好:Windows,Mac和Linux.

我没有任何性能信息,但我相信#pragma和include guard之间的区别与解析C++语法的速度相比毫无结果.这是真正的问题.例如,尝试使用C#编译器编译相同数量的文件和行,以查看差异.

最后,使用后卫或者pragma,根本不重要.


Kla*_*aim 5

性能优势在于,一旦读取了#pragma,就不必重新打开文件。使用警卫,编译器必须打开文件(这可能会花费很多时间),以获取不应再包含其内容的信息。

这只是理论上的原因,因为对于每个编译单元,某些编译器将不会自动打开没有任何读取代码的文件。

无论如何,并非所有编译器都如此,因此理想情况下,一旦跨平台代码完全不标准/没有标准化的定义和效果,就必须避免使用#pragma。但是,实际上,这确实比后卫更好。

最后,更好的建议是确保同时使用编译指示和防护,而不必在这种情况下检查每个编译器的行为,从而确保从编译器获得最佳速度。

#ifndef NR_TEST_H
#define NR_TEST_H
#pragma once

#include "Thing.h"

namespace MyApp
{
 // ...
}

#endif
Run Code Online (Sandbox Code Playgroud)

这样一来,您可以同时兼顾两者(跨平台和帮助编译的速度)。

由于键入时间较长,我个人使用了一种工具来帮助您以一种非常灵巧的方式生成所有内容(Visual Assist X)。

  • @user1095108一些编译器将使用标头保护作为分隔符来了解文件是否只包含必须实例化一次的代码。如果某些代码位于标头保护之外,则整个文件可能被认为可以多次实例化。如果同一个编译器一次不支持 pragma,那么它将忽略该指令。因此,将编译指示放入标头防护中是确保至少可以“优化”标头防护的最通用方法。 (3认同)
  • 为什么你把 `pragma` 放在 `ifndef` 之后?有好处吗? (2认同)

小智 5

不总是.

http://gcc.gnu.org/bugzilla/show_bug.cgi?id=52566有两个文件的好例子,两个文件都包含在内,但由于时间戳和内容相同(文件名不同),误认为是相同的.

  • 那将是编译器中的一个错误.(试图采取不应该采取的捷径). (12认同)
  • `#pragma once`是非标准的,所以无论编译器决定做什么都是"正确的".当然,那么我们可以开始谈论什么是"预期的"和什么是"有用的". (4认同)