#pragma once 与包含守卫? [复制]

Posted

技术标签:

【中文标题】#pragma once 与包含守卫? [复制]【英文标题】:#pragma once vs include guards? [duplicate] 【发布时间】:2009-07-17 15:18:03 【问题描述】:

我正在开发一个已知只能在 Windows 上运行并在 Visual Studio 下编译的代码库(它与 excel 紧密集成,所以它不会去任何地方)。我想知道我是否应该使用传统的包含守卫或使用#pragma once 作为我们的代码。我认为让编译器处理#pragma once 会产生更快的编译速度,并且在复制和粘贴时更不容易出错。它也稍微不那么难看;)

注意:为了获得更快的编译时间,我们可以使用Redundant Include Guards,但这会增加包含文件和包含文件之间的紧密耦合。通常没关系,因为防护应该基于文件名,并且只有在您需要更改包含名称时才会更改。

【问题讨论】:

【参考方案1】:

我认为它不会对编译时间产生显着影响,但 #pragma once 在编译器中得到了很好的支持,但实际上并不是标准的一部分。预处理器可能会更快一些,因为它更容易理解您的确切意图。

#pragma once 更不容易出错,输入的代码也更少。

要加快编译时间,只需转发声明而不是尽可能包含在 .h 文件中。

我更喜欢使用#pragma once

看到这个wikipedia article about the possibility of using both。

【讨论】:

根据***,一些编译器优化编译指示一次,一些(如 gcc)还优化包含守卫。我的直觉说,不管你的代码已经在使用什么系统。我使用 BOTH pragma 一次和警卫。 @Donnie - 这是 gcc 的预处理器适用于包括警卫的优化。它会自动检测头文件中的包含保护,如果有,它会将文件视为具有 #pragma 一次。如果它再次被包含,并且同时包含保护没有未定义,则不会重新打开或重新解析文件。 #pragma once 不可靠(不同的文件层次结构视角(当前工作目录)、软链接和硬链接、网络文件系统,甚至名称冲突——尝试使用名为 string.h 的文件或类似的东西--)。在不考虑速度的情况下,您可以使用一个脚本来替换文件中的任何 %INCLUDE_GUARD% 来替换自动管理的保护符号;您可以将标头编写为@​​987654328@,并且由于您已经拥有预处理器条件对,因此最终标头不会流动,并且编译器会在诊断中发出正确的行号。 我不明白#pragma once 所谓的可靠性问题。如果我有两个不同的标题foo/string.hbar/string.h,那么放置#pragma once 意味着我可以将它们中的每一个都包含一次,但同时包含它们也可以。如果我使用包含保护,那么我可能会在两个文件中写入类似#ifndef STRING_H 的内容 - 这意味着我不能同时包含 foo/string.h 和 bar/string.h。 @Brandin 如果你有两个文件都命名为string.h,那么如果你还没有使用命名空间,那么你可能最终会出现内部名称冲突,因为两个具有该名称的文件都可能做一些涉及字符串的事情,如果你使用命名空间,在我看来,你应该在包含保护标识符中包含命名空间。【参考方案2】:

我只是想在这个讨论中补充一点,我只是在 VS 和 GCC 上编译,并且曾经使用包含保护。我现在已经切换到#pragma once,对我来说唯一的原因不是性能、可移植性或标准,因为只要 VS 和 GCC 支持它,我真的不在乎什么是标准,那就是:

#pragma once 减少了出现错误的可能性。

很容易将一个头文件复制并粘贴到另一个头文件,修改它以适应自己的需要,而忘记更改包含保护的名称。两者都包含后,您需要一段时间才能找到错误,因为错误消息不一定清楚。

【讨论】:

这件事发生在我身上,令人沮丧。花了几个小时才弄清楚。 这是正确的原因。忘记性能——我们应该使用#pragma once,因为它更不容易出错。我突然想到,如果编译器已经在跟踪包含守卫,那么当他们看到使用相同宏名称的不同文件时,他们已经做了大部分必要的工作来发出警告。【参考方案3】:

#pragma once无法修复的错误。永远不要使用它。

如果您的#include 搜索路径足够复杂,编译器可能无法区分具有相同基本名称的两个标头(例如a/foo.hb/foo.h),因此其中一个标头中有#pragma once将抑制 both。它也可能无法分辨出两个不同的相对包含(例如,#include "foo.h"#include "../a/foo.h" 指的是同一个文件,因此 #pragma once 将无法在应该包含冗余包含时抑制它。

这也会影响编译器避免使用#ifndef 保护重读文件的能力,但这只是一种优化。使用#ifndef 保护,编译器可以安全地读取任何它确定它已经看到的文件;如果它是错误的,它只需要做一些额外的工作。只要没有两个头文件定义相同的保护宏,代码就会按预期编译。如果两个头文件确实定义了相同的保护宏,程序员可以进去修改其中一个。

#pragma once 没有这样的安全网——如果编译器对头文件的标识有误,无论哪种方式,程序都将无法编译。如果您遇到此错误,您唯一的选择是停止使用#pragma once,或重命名其中一个标题。标头的名称是您的 API 合同的一部分,因此重命名可能不是一种选择。

(为什么这是不可修复的简短版本是,Unix 和 Windows 文件系统 API 都没有提供任何机制来保证告诉您两个绝对路径名是否引用同一个文件。如果您认为可以使用 inode 编号,对不起,您错了。)

(历史记录:大约 12 年前,当我有权这样做时,我没有将 #pragma once#import 从 GCC 中删除的唯一原因是 Apple 的系统头文件依赖于它们。回想起来,这不应该阻止我。)

(因为这在评论线程中出现了两次:GCC 开发人员确实付出了相当多的努力来使#pragma once 尽可能可靠;请参阅GCC bug report 11569。但是,当前版本的 GCC 中的实现在合理的情况下仍然可能失败,例如构建农场遭受时钟偏差。我不知道任何其他编译器的实现是什么样的,但我不希望有人做得更好 em>.)

【讨论】:

在技术层面上我同意你的观点,但是如果两个包含守卫具有相同的名称,因为文件具有相同的名称(这很可能),没有任何胜利。我弄错了吗? 仅供参考这个答案is being discussed on Reddit。人们不相信你的论点(我认为他们有道理)。 @zwol “除非两次包含同一个文件正是程序员想要的,否则会导致编译失败。”这对我来说似乎不是问题,因为这样的文件不会有包含保护,除非在极少数情况下。这种情况可以通过不对该文件使用#pragma once 来解决。 @zwol 没关系,如果你这样做,那么它不应该有#pragma once :) 再次,必须为那个 pragma 定义某种理智的语义。如果您想多次包含相同的内容,请不要将#pragma once 放在里面:) @zwol 很抱歉,但如果 A/foo.h 和 B/foo.h 相同 并且 具有包含防护,那么包含防护也必然相同并且无论什么宏定义生效,第二个包含仍然会被忽略,除非这些定义以某种方式改变了包含保护的含义(可以吗?可以吗?)。出于预处理器“计算”目的而多次包含的 Boost 源没有包含守卫 IIRC,他们拥有它们是荒谬的。所以,除非我不理解你,否则我认为这不是一个真正的问题。【参考方案4】:

直到#pragma once 成为标准的那一天(这目前不是未来标准的优先事项),我建议你使用它并使用守卫,这样:

#ifndef BLAH_H
#define BLAH_H
#pragma once

// ...

#endif

原因是:

#pragma once 不是标准的,因此某些编译器可能不提供该功能。也就是说,所有主要编译器都支持它。如果编译器不知道它,至少它会被忽略。 由于#pragma once 没有标准行为,您不应假设所有编译器的行为都相同。守卫将至少确保基本假设对于至少为守卫实现所需预处理器指令的所有编译器都是相同的。 在大多数编译器上,#pragma once 将加快编译(一个 cpp),因为编译器不会重新打开包含该指令的文件。因此,将它放在文件中可能会有所帮助,也可能没有帮助,具体取决于编译器。我听说 g++ 可以在检测到警卫时进行相同的优化,但必须确认。

将两者结合使用,您可以充分利用每个编译器。

现在,如果您没有一些自动脚本来生成守卫,使用#pragma once 可能更方便。只知道这对可移植代码意味着什么。 (我正在使用 VAssistX 快速生成守卫和 pragma 一次)

您几乎应该始终以可移植的方式考虑您的代码(因为您不知道未来是由什么组成的),但如果您真的认为它不应该使用另一个编译器进行编译(用于非常特定的嵌入式硬件的代码例如)那么您应该只检查有关#pragma once 的编译器文档以了解您真正在做什么。

【讨论】:

谁说#pragma once 会成为标准?另外,有多少编译器不跟踪头文件是否完全包含在包含防护中?换句话说 - 有没有人衡量过#pragma 是否真的在现实中有所作为? @Richard,我有,而且在某些情况下确实如此 - 我们的项目有 5500 个包含约 1/2 的冗余。 @DarioP:识别它的编译器不在乎。当包含防护之外的东西存在时,不识别它的编译器有禁用包含防护优化的风险。没有优化的编译器会在重复包含时跳过守卫内的所有内容,因此对里面的内容进行较少的处理。 微软似乎已经优化了他们的编译器。来自 Microsoft 的 VS2015 文档:“在同一文件中同时使用 #include 保护习语和 #pragma once 没有任何优势。编译器识别 #include 保护习语并以与 #pragma once 相同的方式实现多重包含优化如果在成语的标准形式之前或之后没有非注释代码或预处理器指令,则指令"msdn.microsoft.com/en-us/library/4141z1cx.aspx 这里的“#pragma once”永远不会被正确评估,所以是多余的,因为守卫总是会覆盖它。不要打扰,只需在更简单的项目上使用老式的守卫或仅编译指示一次。【参考方案5】:

从软件测试人员的角度来看

#pragma once 比包含保护更短,更不容易出错,大多数编译器都支持它,而且有人说它编译得更快(这不是真的[不再])。

但我仍然建议您使用标准的 #ifndef 包括警卫。

为什么是#ifndef

考虑这样一个人为的类层次结构,其中每个类 ABC 都位于自己的文件中:

啊.h

#ifndef A_H
#define A_H

class A 
public:
  // some virtual functions
;

#endif

b.h

#ifndef B_H
#define B_H

#include "a.h"

class B : public A 
public:
  // some functions
;

#endif

c.h

#ifndef C_H
#define C_H

#include "b.h"

class C : public B 
public:
  // some functions
;

#endif

现在假设您正在为您的类编写测试,并且您需要模拟真正复杂的类B 的行为。一种方法是使用例如google mock 编写mock class 并将其放在目录mocks/b.h 中。请注意,类名没有改变,只是存储在不同的目录中。但最重要的是包含保护的名称与原始文件 b.h 中的名称完全相同。

模拟/b.h

#ifndef B_H
#define B_H

#include "a.h"
#include "gmock/gmock.h"

class B : public A 
public:
  // some mocks functions
  MOCK_METHOD0(SomeMethod, void());
;

#endif

有什么好处?

使用这种方法,您可以模拟类B 的行为,而无需触及原始类或告诉C。您所要做的就是将目录mocks/ 放入编译器的包含路径中。

为什么不能用 #pragma once 完成?

如果您使用 #pragma once,您会遇到名称冲突,因为它无法保护您避免定义类 B 两次,一次是原始版本,一次是模拟版本。

【讨论】:

这对于任何大型项目都不实用。保持 B up2date 会很痛苦,更不用说这违反了一个定义规则 @parapurarajkumar 没有违反 ODR,因为如果您在 b.h 之前包含模拟/b.h,则预处理器将完全跳过 b.h,只留下一个 B 类。我想知道您是否有一种不那么“痛苦”的方法测试 B 类。您为测试“任何大型项目”提出什么策略? 没有人覆盖“gmock.h”。它是我们要用来模拟一个类的 Testframework 的一部分。我认为我们在谈论两件不同的事情。重点是采用现有代码或类层次结构,并将链的一部分替换为模拟器或模拟类作为测试。这样您就可以有效地测试非测试代码。它不像单元测试,您只测试一个类,而是模拟允许您做的是系统测试。如果你的类层次结构使用经典的 #ifndef 包含守卫,你可以在测试中用模拟替换一个类。 嗯,如果原始 b.h 和模拟 b.h 都在编译器中包含路径 - 会不会与 #include "b.h" 发生名称冲突?如果您从测试项目的包含目录中删除原始b.h 路径,以便只拾取模拟b.h,那么#pragma once 是否也可以工作? IMO 如果您将 mocks/b.h 添加到包含路径,您还应该删除原始的 b.h 而不是依赖包含保护。您的解决方案与符号插入一样蹩脚:-/【参考方案6】:

如果您确定永远不会在不支持它的编译器中使用此代码(Windows/VS、GCC 和 Clang 是 支持它的编译器示例),那么您当然可以毫无顾虑地使用#pragma 一次。

您也可以同时使用两者(参见下面的示例),以便在兼容系统上获得可移植性和编译速度

#pragma once
#ifndef _HEADER_H_
#define _HEADER_H_

...

#endif

【讨论】:

#pragma once 得到包括 GCC 在内的许多不同编译器的良好支持 是的,但它并未得到普遍支持。始终支持包含守卫...取决于您希望代码的可移植性。 我真的不想要两者,这只会让代码难看。只要可移植性不是问题,我认为代码卫生问题就是下一个问题。 可移植性并不是什么大问题,即使您确实关心它。如果您目前只为 Windows 编写代码,那么您的代码中可能有其他东西比 #pragma once 更紧密地使您与 Windows 联系在一起。如果有一天你确实移植了一些代码,那么编写一个遍历你的标题并用基于文件名和路径的包含保护一次替换所有使用 #pragma 的 Perl 脚本并不难. 另一方面,如果项目的一部分正在编写最终可能在其他地方使用的位(例如数学库、api 包装器等),则需要可移植性。你太担心你的代码应该如何看,而不是它应该如何工作......尤其是在人们已经学会忽略的文件的顶部。【参考方案7】:

在就#pragma once#ifndef 守卫之间假定的性能权衡与正确与否的论点进行了扩展讨论之后(基于最近的一些灌输,我站在#pragma once 一边),我决定最终测试#pragma once 更快的理论,因为编译器不必尝试重新#include 已包含的文件。

为了测试,我自动生成了 500 个具有复杂相互依赖关系的头文件,并有一个 .c 文件,其中 #includes 全部。我以三种方式运行测试,一次只使用#ifndef,一次使用#pragma once,一次使用两者。我在一个相当现代的系统上进行了测试(运行 OSX 的 2014 MacBook Pro,使用 XCode 捆绑的 Clang,带有内部 SSD)。

一、测试代码:

#include <stdio.h>

//#define IFNDEF_GUARD
//#define PRAGMA_ONCE

int main(void)

    int i, j;
    FILE* fp;

    for (i = 0; i < 500; i++) 
        char fname[100];

        snprintf(fname, 100, "include%d.h", i);
        fp = fopen(fname, "w");

#ifdef IFNDEF_GUARD
        fprintf(fp, "#ifndef _INCLUDE%d_H\n#define _INCLUDE%d_H\n", i, i);
#endif
#ifdef PRAGMA_ONCE
        fprintf(fp, "#pragma once\n");
#endif


        for (j = 0; j < i; j++) 
            fprintf(fp, "#include \"include%d.h\"\n", j);
        

        fprintf(fp, "int foo%d(void)  return %d; \n", i, i);

#ifdef IFNDEF_GUARD
        fprintf(fp, "#endif\n");
#endif

        fclose(fp);
    

    fp = fopen("main.c", "w");
    for (int i = 0; i < 100; i++) 
        fprintf(fp, "#include \"include%d.h\"\n", i);
    
    fprintf(fp, "int main(void)int n;");
    for (int i = 0; i < 100; i++) 
        fprintf(fp, "n += foo%d();\n", i);
    
    fprintf(fp, "return n;");
    fclose(fp);
    return 0;

现在,我的各种测试运行:

folio[~/Desktop/pragma] fluffy$ gcc pragma.c -DIFNDEF_GUARD
folio[~/Desktop/pragma] fluffy$ ./a.out 
folio[~/Desktop/pragma] fluffy$ time gcc -E main.c  > /dev/null

real    0m0.164s
user    0m0.105s
sys 0m0.041s
folio[~/Desktop/pragma] fluffy$ time gcc -E main.c  > /dev/null

real    0m0.140s
user    0m0.097s
sys 0m0.018s
folio[~/Desktop/pragma] fluffy$ time gcc -E main.c  > /dev/null

real    0m0.193s
user    0m0.143s
sys 0m0.024s
folio[~/Desktop/pragma] fluffy$ gcc pragma.c -DPRAGMA_ONCE
folio[~/Desktop/pragma] fluffy$ ./a.out 
folio[~/Desktop/pragma] fluffy$ time gcc -E main.c  > /dev/null

real    0m0.153s
user    0m0.101s
sys 0m0.031s
folio[~/Desktop/pragma] fluffy$ time gcc -E main.c  > /dev/null

real    0m0.170s
user    0m0.109s
sys 0m0.033s
folio[~/Desktop/pragma] fluffy$ time gcc -E main.c  > /dev/null

real    0m0.155s
user    0m0.105s
sys 0m0.027s
folio[~/Desktop/pragma] fluffy$ gcc pragma.c -DPRAGMA_ONCE -DIFNDEF_GUARD
folio[~/Desktop/pragma] fluffy$ ./a.out 
folio[~/Desktop/pragma] fluffy$ time gcc -E main.c  > /dev/null

real    0m0.153s
user    0m0.101s
sys 0m0.027s
folio[~/Desktop/pragma] fluffy$ time gcc -E main.c  > /dev/null

real    0m0.181s
user    0m0.133s
sys 0m0.020s
folio[~/Desktop/pragma] fluffy$ time gcc -E main.c  > /dev/null

real    0m0.167s
user    0m0.119s
sys 0m0.021s
folio[~/Desktop/pragma] fluffy$ gcc --version
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.12.sdk/usr/include/c++/4.2.1
Apple LLVM version 8.1.0 (clang-802.0.42)
Target: x86_64-apple-darwin17.0.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin

如您所见,带有#pragma once 的版本的预处理速度确实比#ifndef 稍快——只有一个,但是差异可以忽略不计,并且会被实际构建和链接代码所需的时间。也许如果代码库足够大,它实际上可能会导致构建时间的差异只有几秒钟,但是现代编译器能够优化#ifndef 守卫,操作系统具有良好的磁盘缓存这一事实,以及存储技术速度的提高,似乎性能论点是没有实际意义的,至少在当今时代的典型开发人员系统上是这样。较旧且更具异国情调的构建环境(例如,托管在网络共享上的标头,从磁带构建等)可能会在一定程度上改变等式,但在这些情况下,首先简单地创建一个不那么脆弱的构建环境似乎更有用。

事实上,#ifndef 是标准化的标准行为,而#pragma once 不是,#ifndef 也处理奇怪的文件系统和搜索路径的极端情况,而#pragma once 可能会被某些事情搞糊涂,导致程序员无法控制的错误行为。 #ifndef 的主要问题是程序员为他们的守卫选择了不好的名字(名称冲突等),即使这样,API 的使用者也很有可能使用 #undef 覆盖那些不好的名字 - 这不是一个完美的解决方案,也许吧,但它可能,而如果编译器错误地剔除#include#pragma once 就没有办法了。

因此,尽管 #pragma once 明显(略)快,但我不同意这本身就是使用它而不是 #ifndef 守卫的理由。

编辑:感谢@LightnessRacesInOrbit 的反馈,我增加了头文件的数量并将测试更改为仅运行预处理器步骤,从而消除了由编译和链接过程(以前很简单,现在不存在)。正如预期的那样,差异大致相同。

【讨论】:

“可以忽略不计”?您将构建时间缩短了一半。 您的答案没有提供足够的上下文来判断节省是否会随着项目的增长保持恒定的 0.04 秒,或者随着项目的增长保持恒定的 50%,或者介于两者之间。因此,它不是一个非常有用的基准,“可忽略不计”的结论目前不受支持。我只是提到它,因为我会对真正的结论非常感兴趣! 这是一个很好的观点——它只测量包含守卫的速度,而不是预处理器的其余部分。我认为很明显,生成的代码只是在测试那个方面,因为警戒线之间没有太多的东西。也许我可以扩展生成的代码,使每个文件都有几千个随机生成的模板函数和类,这会导致不同的不切实际的综合测试。 甚至不仅仅是预处理器——你正在做一个完整的编译和链接。我们只是无法从这些结果中知道“不相关的常量开销”是什么,因此结果并没有真正告诉我们任何实际用途。可悲! @LightnessRacesinOrbit 我已将测试更改为拥有更多#include 文件并仅运行预处理器。毫不奇怪,时差大致相同(现在是小得多的部分)。【参考方案8】:

我通常不会为#pragma once 烦恼,因为我的代码有时必须使用 MSVC 或 GCC 以外的东西进行编译(嵌入式系统的编译器并不总是有 #pragma)。

所以无论如何我都必须使用#include 守卫。正如一些答案所建议的那样,我也可以使用#pragma once,但似乎没有太多理由,而且它通常会在不支持它的编译器上引起不必要的警告。

我不确定 pragma 可以节省多少时间。我听说编译器通常已经识别出头文件何时只有保护宏之外的 cmets 并且在这种情况下将执行 #pragma once 等效项(即,不再处理文件)。但我不确定这是真的还是编译器的一个例子可以做这个优化。

在任何一种情况下,我都可以更轻松地使用 #include 保护,它可以在任何地方工作,而不必再担心它。

【讨论】:

我很好奇哪个编译器不支持它。似乎大多数编译器都会这样做,但我不知道哪个没有。【参考方案9】:

有一个related question,I answered:

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

我也在此处添加答案,以防有人偶然发现这个问题而不是其他问题。

【讨论】:

我认为这是不使用#pragma 一次的最令人信服的理由。谢谢! @ShitalShah 如果您的构建系统复制文件,这只是一个考虑因素 修复 #pragma once 实现,它应该使用可靠的校验和而不是粗略的文件名。【参考方案10】:

我认为您应该做的第一件事是检查这是否真的会有所作为,即。您应该首先测试性能。谷歌中的一项搜索抛出了this。

在结果页面中,这些列对我来说有点偏离,但很明显,至少在 VC6 之前,微软没有实现其他工具正在使用的包含保护优化。与包含防护在外部的情况相比,包含防护在内部的时间是外部防护的 50 倍(外部包含防护至少与#pragma 一样好)。但让我们考虑一下这可能产生的影响:

根据提供的表格,打开包含并检查它的时间是 #pragma 等效项的 50 倍。但在 1999 年,实际执行此操作的时间为每个文件 1 微秒!

那么,一个 TU 会有多少个重复的标头?这取决于您的风格,但如果我们说平均 TU 有 100 个重复项,那么在 1999 年我们可能会为每个 TU 支付 100 微秒。随着 HDD 的改进,现在这可能会显着降低,但即使这样,使用预编译的头文件和正确的依赖项跟踪项目的总累积成本几乎肯定是构建时间的一个微不足道的部分。

现在,另一方面,如果您改用不支持 #pragma once 的编译器,那么这可能不太可能,那么请考虑更新整个源代码库以包含保护需要多长时间而不是#pragma?

微软没有理由不能像 GCC 和所有其他编译器那样实现包含保护优化(实际上任何人都可以确认他们的最新版本是否实现了这一点?)。恕我直言,#pragma once 除了限制您对替代编译器的选择之外几乎没有什么作用。

【讨论】:

不要挑剔,但是如果您要移植到不支持 pragma-once 的编译器,编写一个一次性工具来拖网文件并用传统的包含保护替换 #pragma once 可能是微不足道的面对整个移植过程。 “实际上任何人都可以确认他们的最新版本是否实现了这一点?” VS2015 文档中提到它现在确实实现了包括保护优化。 msdn.microsoft.com/en-us/library/4141z1cx.aspx “考虑将整个源库更新为包含守卫而不是#pragma 需要多长时间?” 应该不会花很长时间。让这一切变得简单是我写guardonce的原因。【参考方案11】:

#pragma once 允许编译器在文件再次出现时完全跳过文件 - 而不是在文件到达#include 保护之前解析文件。

因此,语义略有不同,但如果按照它们的预期使用方式使用它们,它们是相同的。

将两者结合起来可能是最安全的方法,因为在最坏的情况下(编译器将未知的编译指示标记为实际错误,而不仅仅是警告),您只需要自己删除 #pragma 即可。

当您将平台限制为“桌面上的主流编译器”时,您可以放心地省略 #include 保护,但我也对此感到不安。

OT:如果您有其他关于加快构建的技巧/经验可以分享,我会很好奇。

【讨论】:

@Peterchen:说编译器需要重新读取包含保护的文件是错误的。编译器第一次解析主体时,它可以记录标头是否具有正确的包含保护。因此,如果它稍后被#included,它可以跳过标题。主要区别在于#pragma 不是标准的,如果您需要使用不支持它的编译器,那么您将陷入痛苦的世界。包含守卫可能发生的更糟糕的情况是性能受到非常轻微的影响。 不是一个真正的痛苦世界。您可以相当轻松地编写一个脚本,将所有出现的#pragma once 替换为包含防护。包含保护可能发生的最糟糕的情况不是性能,而是使用拼写错误的#ifdefs 或复制文件、更改内容并忘记更新#ifdefs。【参考方案12】:

对于那些想使用 #pragma once 并同时包含守卫的人:如果您不使用 MSVC,那么您将不会从 #pragma once 获得太多优化。

并且您不应该将“#pragma once”放入应该包含多次的标题中,每次包含可能具有不同的效果。

Here 是关于#pragma once 用法的详细讨论。

【讨论】:

“你不应该把#pragma once放到一个应该被多次包含的头文件中。” -- 它适用于被多次包含的头文件;这就是重点。我认为你的意思是它不应该用于应该被多次包含的标题每个包含可能具有不同的效果。这方面的例子很少; &lt;assert.h&gt;&lt;cassert&gt; 就是这样一个例子(因为它的行为取决于是否定义了 NDEBUG)。 谢谢,这更清楚了。更新了我的答案。【参考方案13】:

上面Konrad Kleine的解释。

简要总结:

当我们使用# pragma once 时,这是编译器的大部分责任,不允许多次包含它。这意味着,在您在文件中提到 code-sn-p 之后,您就不再需要负责了。

现在,编译器在文件开头查找此代码-sn-p,并跳过它被包含(如果已经包含一次)。这肯定会减少编译时间(平均而言,在巨大的系统中)。但是,在模拟/测试环境中,由于循环等依赖,会使测试用例的实现变得困难。

现在,当我们将#ifndef XYZ_H 用于标头时,开发人员有更多的责任来维护标头的依赖关系。这意味着,每当由于一些新的头文件,存在循环依赖的可能性时,编译器只会在编译时标记一些“undefined ..”错​​误消息,由用户检查实体的逻辑连接/流和纠正不正确的包含。

这肯定会增加编译时间(需要纠正并重新运行)。此外,由于它是在包含文件的基础上工作的,基于“XYZ_H”定义状态,并且如果无法获得所有定义,仍然会抱怨。

因此,为了避免这种情况,我们应该使用 as;

#pragma once
#ifndef XYZ_H
#define XYZ_H
...
#endif

即两者的结合。

【讨论】:

以上是关于#pragma once 与包含守卫? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

需要澄清一下#pragma once

#pragma once 与 #ifndef

#pragma once

#pragma once 等价于 c++builder

C++中防止多次包含头文件:#pragma once和 #indef的区别

C++中防止多次包含头文件:#pragma once和 #indef的区别