#pragma once是一种安全的头文件包含防护方式吗?

428

我读到使用#pragma once会有一些编译器优化,可以加快编译速度。我知道这是非标准的,可能会在跨平台上造成兼容性问题。

大多数现代编译器在非 Windows 平台(如 gcc)是否支持这个优化呢?

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

#pragma once
#ifndef HEADER_H
#define HEADER_H

...

#endif // HEADER_H

我应该关注吗?我是否应该继续花费精力在这件事上?


3
在询问了一个类似的问题之后,我发现#pragma once似乎可以避免VS 2008中的一些类视图问题。出于这个原因,我正在逐步取消所有的包含保护并将它们替换为#pragma once - SmacL
15个回答

392

#pragma once 的一个缺点(除了不是标准的)就是如果您在不同的位置有相同的文件(我们有这种情况,因为我们的构建系统会复制文件),那么编译器将认为这些是不同的文件。


42
但是您也可以在不必创建不同的 #define 名称的情况下,在不同位置拥有两个具有相同名称的文件,通常以 HEADERFILENAME_H 的形式出现。 - Vargas
83
您也可以拥有两个或多个具有相同#define WHATEVER的文件,这会导致无尽的麻烦,这就是我倾向于使用#pragma once的原因。 - Chris Huang-Leaver
156
不具有说服力... 将构建系统更改为不复制文件但使用符号链接的系统,或者在每个翻译单元中仅从一个位置包含相同的文件。听起来更像是你的基础设施混乱需要重新组织。 - Yakov Galka
5
如果您在不同的目录中有相同名称的文件,则 #ifdef 方法将认为它们是同一文件。因此,其中一个有缺点,另一个也有缺点。 - rxantos
5
如果文件不同,那么 #ifdef 宏的值也可能不同。 - Motti
显示剩余8条评论

267

使用#pragma once应该可以在任何现代编译器上工作,但我认为使用标准的#ifndef包含守卫也没有任何问题。它也能正常工作。唯一需要注意的是,在版本 3.4之前,GCC不支持#pragma once

我还发现,至少在GCC上,它会识别标准的#ifndef包含守卫并进行优化,所以它的速度应该不会比#pragma once慢多少。


15
无论如何(至少在使用GCC编译器时),速度都不应该变慢。 - Jason Coco
64
不是以那种方式实现的。相反,如果文件在第一次出现时以 #ifndef 开头,并以 #endif 结尾,gcc 会记住它,并在未来始终跳过该包含文件,而无需打开文件进行处理。 - Jason Coco
15
#pragma once 一般更快,因为文件不需要进行预处理。 ifndef/define/endif 虽然也需要预处理,但是在这个块之后你可以有可编译的东西(理论上)。 - Andrey
19
GCC关于守卫宏优化的文档:http://gcc.gnu.org/onlinedocs/cppinternals/Guard-Macros.html - Adrian
66
为使用包含保护,您需要定义一个新符号,例如#ifndef FOO_BAR_H,通常用于名为 "foo_bar.h" 的文件。如果稍后重命名此文件,是否应相应地调整包含保护以与此约定一致?而且,如果您的代码树中有两个不同位置的 foo_bar.h,则必须为每个文件想出两个不同的符号。简短的答案是使用 #pragma once,如果您确实需要在不支持它的环境中编译,则继续为该环境添加包含保护。 - Brandin
显示剩余15条评论

79

我希望标准库中有类似#pragma once的东西。虽然头文件保护不是一个大问题(但是对于学习语言的人来说可能有点难以理解),但是它似乎是可以避免的小麻烦。

实际上,99.98%的情况下,防止重复包含头文件的行为都是所需的,因此如果编译器自动处理了这个问题,并提供了一个#pragma或其他方式来允许重复包含头文件,那将非常好。

但我们现在只能使用现有的机制(除非你没有#pragma once)。


64
我真正想要的是一个标准的#import指令。 - John
13
标准的导入指令即将推出:http://isocpp.org/blog/2012/11/modules-update-on-work-in-progress-doug-gregor但目前仍未正式发布。我强烈支持此举。 - AHelps
9
“Vaporware”是指虚拟产品。这个词距离现在已经近五年了。也许到了2023年,你会回来看这条评论,并说:“我早就告诉你了。” - doug65536
1
应该将其转换为C++20。 - Ionoclast Brigham
16
并已经成为 C++20 的一部分。 - LNJ
显示剩余4条评论

44

我不知道有没有性能优势,但它肯定是有效的。 我在所有我的C++项目中都使用它(尽管我正在使用MS编译器)。 我发现它比使用...更有效。

#ifndef HEADERNAME_H
#define HEADERNAME_H
...
#endif
它完成相同的工作,不会在预处理器中填充额外的宏。 GCC 官方支持 #pragma once,自3.4版本起

30

GCC自3.4版本开始支持#pragma once,有关更多编译器支持信息,请参见http://en.wikipedia.org/wiki/Pragma_once

我认为使用#pragma once而不是包含保护的主要优点是避免复制粘贴错误。

让我们面对现实吧:我们大多数人都很少从头开始创建一个新的头文件,而是只是复制一个现有的然后根据我们的需求进行修改。使用#pragma once创建一个工作模板比使用包含保护要容易得多。我需要修改模板的次数越少,就越不可能遇到错误。在不同文件中使用相同的包含保护会导致奇怪的编译器错误,并且需要花费一些时间来找出问题所在。

简而言之:#pragma once更容易使用。


12

我使用它并感到很满意,因为我需要输入的内容更少才能创建新的标题。我在三个平台上使用它都很好用:Windows、Mac和Linux。

我没有任何性能信息,但我相信 #pragma 和 include 声明防护的差别将无法与解析 C++ 语法时的速度慢相比。那才是真正的问题。例如,尝试使用 C# 编译器编译相同数量的文件和行,来看看差异。

最终,使用防护或者#pragma都不会有太大影响。


我不喜欢 #pragma once,但我感谢你指出了相关的好处……在一个“正常”的操作环境中,C++语言解析比其他任何语言都要昂贵得多。如果编译时间是个问题,那么没有人会从远程文件系统进行编译。 - Tom
1
关于 C++ 解析速度与 C# 的比较。在 C# 中,你不必为每个小的 C++ 文件解析(字面上的)数千行头文件代码(例如 iostream)。使用预编译头文件可以减小这个问题。 - Eli Bendersky

11

使用'#pragma once'可能没有任何效果(它不受到普遍支持,尽管越来越多的编译器开始支持),因此您仍需要使用有条件的编译代码,在这种情况下,为什么要费劲地使用'#pragma once'呢?编译器可能会自动优化它。但这要取决于您的目标平台。如果您的所有目标都支持它,请继续使用它,但这应该是一个慎重的决定,因为如果您仅使用#pragma而后移到不支持它的编译器上,则会出现问题。


1
我不同意你必须支持守卫的说法。如果你使用#pragma once(或者守卫),那是因为没有它们会引起一些冲突。所以,如果你的链式工具不支持它们,项目就无法编译,这与在旧的K&R编译器上编译一些ansi C时的情况完全相同。你只需要获取一个更高版本的链式工具或更改代码以添加一些守卫。如果程序编译成功但无法正常工作,那才是真正的灾难。 - kriss

6
性能的提升来源于只读取一次 #pragma once。使用头文件保护时,编译器需要打开文件(可能耗费大量时间)以获取信息,以避免再次包含其内容。
这仅是理论,因为某些编译器会自动不打开未读代码的文件,对于每个编译单元。
无论如何,不是所有编译器都适用,因此最好避免在跨平台代码中使用 #pragma once,因为它没有标准化的定义和效果。但实际上,它比头文件保护更好。
最后,为了确保编译器的最佳速度而不必检查每个编译器的行为,请同时使用 pragma once 和头文件保护。您可以在这里了解更多信息。
#ifndef NR_TEST_H
#define NR_TEST_H
#pragma once

#include "Thing.h"

namespace MyApp
{
 // ...
}

#endif

这样做可以兼顾跨平台和编译速度的优点。

由于输入较长,我个人使用一个工具来帮助快速生成代码(Visual Assist X)。


Visual Studio是否不会像原样优化#include保护?其他(更好的?)编译器会这样做,我想这很容易。 - Tom
3
你为什么要把 pragma 放在 ifndef 后面?有什么好处吗? - user1095108
3
@user1095108 一些编译器会使用头文件保护作为分隔符,以确定文件是否仅包含需要实例化一次的代码。如果某些代码在头文件保护之外,则整个文件可能被认为可以实例化多次。如果同一编译器不支持#pragma once,则它将忽略该指令。因此,在头文件保护内放置#pragma once是确保至少可以“优化”头文件保护的最通用方法。 - Klaim

5
并不总是这样。 http://gcc.gnu.org/bugzilla/show_bug.cgi?id=52566 给出了一个很好的例子,两个文件被认为是相同的,因为它们具有相同的时间戳和内容(但文件名不同),但实际上它们应该都被包含。

12
那可能是编译器的一个漏洞(试图采取不该采取的捷径)。 - rxantos
4
#pragma once 是非标准的,所以无论编译器决定做什么都是“正确”的。当然,我们可以开始讨论什么是“预期的”和什么是“有用的”。 - user7610

2
对于那些认为自动一次性包含头文件总是理想的人,我有一个额外的说明:我使用双重或多重包含头文件构建代码生成器已经数十年了。特别是对于协议库存根的生成,我发现拥有一个极其便携和强大的代码生成器且无需额外的工具和语言非常舒适。正如这个博客中的 X-Macros所示,我并不是唯一使用这种方案的开发者。如果没有缺失的自动保护,这将是不可能实现的。请注意,保留HTML标签,但不要写解释。

C++ 模板可以解决这个问题吗?由于 C++ 模板的存在,我很少发现需要使用宏的情况。 - Clearer
2
我们长期的专业经验表明,始终使用成熟的语言、软件和工具基础设施,可以为我们作为服务提供商(嵌入式系统)带来巨大的生产力和灵活性优势。相比之下,那些开发基于C++的嵌入式系统软件和堆栈的竞争对手可能会发现他们的一些开发人员更加快乐。但是我们通常在上市时间、功能和灵活性方面都能多次超越他们。不要低估反复使用同一工具所带来的生产力收益。与此相反,Web-Devs则因过多的框架而受苦。 - Marcel
需要注意的是:在每个头文件中包含守卫/ #pragma once是否违反了DRY原则本身。我可以理解你在X-Macro功能中的观点,但这不是include的主要用途,如果我们坚持DRY原则,难道不应该采取另一种方式,比如头文件未保护/ #pragma multi吗? - caiohamamura
1
DRY代表“不要重复自己”。它关乎人类。机器正在做什么与这个范例无关。C++模板会重复很多,C编译器也会这样做(例如循环展开),每台计算机几乎都在以难以置信的速度重复着几乎所有的事情。 - Marcel
非常好的观点@Marcel。X-Macro概念确实非常有用,为了创建一个旨在在该方案中使用的头文件,需要允许多次包括头文件。总体理解始终很重要。我倾向于区分作为X-Macro编写的头文件和其他经典头文件,这些头文件意味着只包含一次。可能使用命名约定,使生成这些文件的编辑器工具可以区分文件的目的和代码读取器。 - PRouleau

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接