375

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

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

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

#pragma once
#ifndef HEADER_H
#define HEADER_H

...

#endif // HEADER_H

我应该担心吗?我是否应该为此花费更多的精力?

4

15 回答 15

360

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

于 2009-12-22T14:35:00.907 回答
237

使用#pragma once应该适用于任何现代编译器,但我看不出有任何理由不使用标准#ifndef包含防护。它工作得很好。需要注意的是 GCC 在3.4 版本#pragma once之前不支持。

我还发现,至少在 GCC 上,它可以识别标准#ifndefinclude guard 并对其进行优化,因此它不应该比#pragma once.

于 2009-04-24T20:52:49.723 回答
72

我希望#pragma once(或类似的东西)在标准中。包括警卫并不是什么大问题(但它们似乎确实有点难以向学习语言的人解释),但这似乎是一个可以避免的小烦恼。

事实上,由于 99.98% 的情况下,行为是期望的行为,如果防止头文件的多次包含由编译器自动处理,使用 a或其他东西来允许双重包含#pragma once,那就太好了。#pragma

但是我们有我们所拥有的(除了你可能没有#pragma once)。

于 2009-04-24T20:59:35.387 回答
42

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

#ifndef HEADERNAME_H
#define HEADERNAME_H
...
#endif

它做同样的工作并且不会用额外的宏填充预处理器。

GCC从 3.4 版开始#pragma once正式支持。

于 2009-04-24T20:52:46.150 回答
29

GCC#pragma once自 3.4 起支持,请参阅http://en.wikipedia.org/wiki/Pragma_once以获得更多编译器支持。

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

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

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

于 2013-08-28T12:41:19.250 回答
12

我使用它并且对它很满意,因为我需要输入更少的内容来创建一个新的标题。它在三个平台上运行良好:Windows、Mac 和 Linux。

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

最后,使用警卫或pragma,根本不重要。

于 2009-04-25T13:32:36.013 回答
11

使用 ' #pragma once' 可能没有任何效果(并非所有地方都支持它 - 尽管它越来越广泛地支持),所以无论如何您都需要使用条件编译代码,在这种情况下,为什么还要使用 ' #pragma once'?编译器可能无论如何都会对其进行优化。不过,它确实取决于您的目标平台。如果你所有的目标都支持它,那么继续使用它——但这应该是一个有意识的决定,因为如果你只使用 pragma 然后移植到不支持它的编译器,所有的地狱都会崩溃。

于 2009-04-24T20:54:07.973 回答
5

性能优势在于一旦读取 #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)。

于 2009-04-24T21:04:31.287 回答
4

不总是。

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

于 2013-04-11T13:36:58.940 回答
2

在非常大的树上使用 gcc 3.4 和 4.1(有时使用distcc),我还没有看到使用 #pragma 代替标准包含防护或与标准包含防护结合使用时有任何加速。

我真的看不出它有多么值得混淆旧版本的 gcc,甚至其他编译器,因为没有真正的节省。我还没有尝试过所有的各种 delinter,但我敢打赌这会让他们中的许多人感到困惑。

我也希望它早日被采用,但我可以看到这样的论点“当 ifndef 工作得非常好时,为什么我们需要它?”。鉴于 C 的许多黑暗角落和复杂性,包含守卫是最简单、自我解释的事情之一。如果您对预处理器的工作原理有一点了解,它们应该是不言自明的。

但是,如果您确实观察到显着加速,请更新您的问题。

于 2009-04-25T01:56:24.160 回答
2

今天,老式的包括警卫和#pragma once 一样快。即使编译器没有特别对待它们,它仍然会在看到 #ifndef WHATEVER 和 WHATEVER 被定义时停止。今天打开文件非常便宜。即使有改进,也将是毫秒级的。

我只是不使用#pragma 一次,因为它没有任何好处。为避免与其他包含守卫发生冲突,我使用如下内容: CI_APP_MODULE_FILE_H --> CI = Company Initials; APP = 应用程序名称;其余的不言自明。

于 2010-03-04T04:39:26.133 回答
2

主要区别在于编译器必须打开头文件才能读取包含保护。相比之下,pragma once 会导致编译器跟踪文件,并且在遇到同一文件的另一个包含时不执行任何文件 IO。虽然这听起来可以忽略不计,但它可以轻松扩展大型项目,尤其是那些没有良好标题的项目包括学科。

也就是说,现在的编译器(包括 GCC)足够聪明,可以处理一次像 pragma 这样的守卫。即他们不打开文件并避免文件IO惩罚。

在不支持编译指示的编译器中,我看到手动实现有点麻烦..

#ifdef FOO_H
#include "foo.h"
#endif

我个人喜欢#pragma once 方法,因为它避免了命名冲突和潜在的拼写错误的麻烦。相比之下,它也是更优雅的代码。也就是说,对于可移植代码,除非编译器抱怨它,否则两者都应该没有什么坏处。

于 2011-12-17T12:52:52.437 回答
2

如果我们使用 msvc 或 Qt(最高 Qt 4.5),由于 GCC(最高 3.4)、msvc 都支持#pragma once,我看不出没有不使用的理由#pragma once

源文件名通常与类名相同,而且我们知道,有时我们需要重构,重命名类名,然后我们也不得不更改#include XXXX,所以我认为手动维护#include xxxxx不是一个聪明的工作。即使使用 Visual Assist X 扩展,维护“xxxx”也不是必要的工作。

于 2013-09-14T12:39:08.413 回答
2

对那些认为总是需要自动一次性包含头文件的人的补充说明:几十年来,我使用双重或多重包含头文件来构建代码生成器。特别是对于协议库存根的生成,我发现拥有一个非常便携且功能强大的代码生成器非常舒适,无需额外的工具和语言。正如博客 X-Macros所示,我不是唯一使用此方案的开发人员。如果没有丢失的自动防护,这将是不可能的。

于 2017-06-14T06:40:16.947 回答
2

我使用 #ifndef/#define 包含使用包含这样的UUID的符号的保护:

#ifndef ARRAY__H_81945CB3_AEBB_471F_AC97_AB6C8B220314
#define ARRAY__H_81945CB3_AEBB_471F_AC97_AB6C8B220314 /* include guard */


#endif

我一直使用能够自动生成 UUID 的编辑器。这可以防止与其他库中具有相同基本名称的文件发生名称冲突,并检测完全相同的文件是否放置在文件系统内的多个位置。

不利的一面是增加了表的大小,因为符号要大得多,但我还没有看到它有问题。

于 2021-05-28T20:57:46.027 回答