#pragma once
来编写我们的代码。我认为让编译器处理#pragma once
将会产生更快的编译速度,并且在复制和粘贴时也不容易出错。而且稍微好看一点;)
注意:为了获得更快的编译时间,我们可以使用Redundant Include Guards,但这会在被包含文件和包含文件之间添加紧密耦合。通常这没问题,因为保护应该基于文件名,并且只有在需要更改包含名称时才会更改。
#pragma once
来编写我们的代码。我认为让编译器处理#pragma once
将会产生更快的编译速度,并且在复制和粘贴时也不容易出错。而且稍微好看一点;)
注意:为了获得更快的编译时间,我们可以使用Redundant Include Guards,但这会在被包含文件和包含文件之间添加紧密耦合。通常这没问题,因为保护应该基于文件名,并且只有在需要更改包含名称时才会更改。
我不认为这会显著影响编译时间,但是#pragma once
在各个编译器中得到了很好的支持,但它实际上并不是标准的一部分。预处理器可能会更快,因为它更容易理解您的确切意图。
#pragma once
更不容易出错,而且打代码也更少。
要加快编译时间,请尽可能使用前向声明而不是在.h文件中包含它们。
我喜欢使用#pragma once
。
我想在这个讨论中补充一点,我只是在VS和GCC上进行编译,并使用了包含保护。现在我已经转而使用#pragma once
,这样做的唯一原因不是性能、可移植性或标准,因为我并不关心它的标准,只要VS和GCC支持就可以了,原因如下:
#pragma once
减少了引入错误的可能性。
将一个头文件复制并粘贴到另一个头文件中,并修改以适应自己的需求,然后忘记更改包含保护的名称非常容易。一旦两者都被包含,查找错误需要花费一段时间,因为错误消息并不一定清晰。
#pragma once
,因为它更少出错。我想到了,如果编译器已经在追踪 include guards,那么他们已经完成了大部分必要的工作,当他们看到不同的文件使用相同的宏名称时发出警告。 - rieux#pragma once
存在无法修复的错误,不应该使用。如果您的#include
搜索路径非常复杂,编译器可能无法区分两个具有相同基本名称的头文件(例如a/foo.h
和b/foo.h
),因此其中一个头文件中的#pragma once
将会抑制两个。它也可能无法确定两个不同的相对包含(例如#include "foo.h"
和#include "../a/foo.h"
)是否指向同一文件,因此#pragma once
将无法在应该抑制冗余包含时抑制它。
(为什么这是无法修复的问题的简短版本是,Unix和Windows文件系统API都没有提供任何机制来保证告诉你两个绝对路径名是否指向同一个文件。如果你认为inode号码可以用于此,抱歉,你错了。)
(历史注释:我之所以没有在12年前有权利这样做时将#pragma once
和#import
从GCC中删除,唯一的原因是苹果公司的系统头文件依赖它们。回想起来,那不应该阻止我。)
(由于这在评论线程中已经出现了两次:GCC开发人员确实花了很多精力使#pragma once
尽可能可靠;请参见GCC bug report 11569。然而,在当前版本的GCC中,实现仍然可能在合理的条件下失败,例如遭受时间偏差的构建农场。我不知道其他编译器的实现情况,但我不会期望有人做得更好。)
#pragma once
来解决。 - Jordan Melo#pragma once
:) 要再次强调的是,必须对这个编译指示符进行一些明确的语义定义。如果你想多次包含同一内容,不要在其中加入 #pragma once
。 - Kuba hasn't forgotten Monica在#pragma once
成为标准之前(当前未来标准的优先事项之一),建议您同时使用它和使用宏保护,这样:
#ifndef BLAH_H
#define BLAH_H
#pragma once
// ...
#endif
原因如下:
#pragma once
不是标准,所以有些编译器可能不提供该功能。尽管如此,所有主要的编译器都支持它。如果编译器不认识它,至少它会被忽略。#pragma once
没有标准行为,您不应假设其行为在所有编译器上都相同。使用头文件保护可以确保所有实现了所需预处理器指令的编译器至少具有相同的基本假设。#pragma once
可以加速编译(一个 cpp 文件),因为编译器不会重新打开包含该指令的文件。因此,将其放在文件中可能有助于加快编译速度,也可能不会,这取决于编译器。我听说 g++ 在检测到头文件保护时也可以进行相同的优化,但需要确认。同时使用两者能够充分发挥它们各自的优点。
如果您没有一些自动脚本来生成头文件保护,那么使用 #pragma once
可能更方便。只需了解对于可移植代码意味着什么即可。(我正在使用 VAssistX 快速生成头文件保护和 #pragma once)
您几乎总是应该以可移植的方式考虑您的代码(因为您不知道未来是什么),但如果您确实认为它不适用于与另一个编译器编译(例如针对非常特定的嵌入式硬件的代码),那么您应该查看有关 #pragma once
的编译器文档,了解您正在做什么。
#pragma once
比include保护更短,更少出错,在大多数编译器中都被支持,并且一些人认为它编译得更快(这已经不再是真的了)。
但我仍建议您使用标准的#ifndef
include保护。
#ifndef
?考虑一个像这样的编造的类层次结构,其中每个类 A
,B
和 C
都存在于各自的文件中:
#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
#ifndef C_H
#define C_H
#include "b.h"
class C : public B {
public:
// some functions
};
#endif
现在假设你正在编写类的测试,并且需要模拟非常复杂的类B
的行为。一种方法是编写一个mock类,例如使用Google Mock,并将其放入目录mocks/b.h
中。请注意,类名未更改,但仅存储在不同的目录中。但最重要的是include guard与原始文件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.h
和模拟的b.h
都在编译器的包含路径中-那么#include "b.h"
不会有名称冲突吗? 如果您从测试项目的包含目录中删除原始的b.h
路径,以便只选择模拟的b.h
,那么#pragma once
也会起作用吗? - Samaursamocks/b.h
加入到包含路径中,那么你应该删除原始的b.h
,而不是依赖于包含保护。你的解决方案就像符号重定位一样令人失望。 - Navin在讨论使用#pragma once
和#ifndef
宏定义作为头文件保护方式的性能问题时,涉及到正确性的争议(我站在#pragma once
这一面,基于一些比较近期的背景知识)。最终,我决定测试一下#pragma once
是否更快,因为编译器不必重新尝试包含已经被包含过的文件。
为了测试,我自动生成了500个具有复杂相互依赖关系的头文件,并且有一个.c
文件将它们全部#include
。我进行了三次测试,一次只使用#ifndef
,一次只使用#pragma once
,一次同时使用两者。测试在一台相对较新的系统上运行(2014年款的MacBook Pro,运行OSX,使用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”保护,操作系统拥有良好的磁盘缓存,以及存储技术的不断提速,性能问题似乎已经不再重要,至少在当前典型的开发者系统中是这样。旧的和更奇特的构建环境(例如托管在网络共享中的头文件,从磁带构建等)可能会在某种程度上改变这个公式,但在这些情况下,似乎更有用的是首先建立一个不那么脆弱的构建环境。#include
规则的病态表现,这在现实生活中可能永远不会出现。重点是测量#ifndef
守卫对#pragma once
的开销有多大,任何代码库如果超过0.04秒就存在更大的问题。 - fluffy如果您确定永远不会在不支持它的编译器(例如Windows / VS,GCC和Clang是支持它的编译器)中使用此代码,则可以放心地使用#pragma once。
您还可以同时使用两者(见下面的示例),以便在兼容系统上获得可移植性和编译速度加快。
#pragma once
#ifndef _HEADER_H_
#define _HEADER_H_
...
#endif
通常我不使用 #pragma once
,因为我的代码有时需要与除了MSVC或GCC之外的编译器编译(嵌入式系统的编译器并不总是支持 #pragma)。
所以我仍然需要使用 #include guards。一些答案建议我也可以使用 #pragma once
,但似乎没有太多理由这样做,而且它会在那些不支持它的编译器上经常引起不必要的警告。
我不确定 #pragma 能带来多少时间节省。我听说编译器通常已经能识别出头文件中除了 guard 宏之外只有注释的情况,并在这种情况下执行 #pragma once
的等效操作(即,永远不再处理该文件)。但我不确定这是真的还是只是编译器“可能”进行的优化。
无论哪种情况,对我来说使用 #include guards 更容易,在任何地方都可以工作,也不必进一步担心它。
在结果页面中,列对我来说稍微有点偏离,但很明显至少到VC6为止,Microsoft没有实现其他工具正在使用的包含保护优化。 对于内部包含保护,所需时间比外部包含保护长50倍(外部包含保护至少与#pragma一样好)。 但是让我们考虑这可能产生的影响:
根据所提供的表格,打开包含并进行检查所需的时间是#pragma等效方案的50倍。 但是,实际执行此操作所需的时间在1999年测量为每个文件1微秒!
那么,每个TU会有多少个重复的头文件?这取决于你的编码风格,但是如果我们假设一个平均的TU有100个副本,那么在1999年,我们每个TU可能要花费100微秒。随着HDD的改进,现在这个数字可能会显著降低,但即使如此,在使用预编译头文件和正确的依赖跟踪的情况下,对于项目来说,总累计成本几乎肯定不是构建时间的重要部分。
另一方面,尽管可能性很小,但如果您将来转移到不支持#pragma once
的编译器,请考虑更新整个源代码库以使用包含保护符而不是#pragma需要多长时间?
没有理由Microsoft不能像GCC和其他编译器一样实现包含保护符优化(实际上有人能确认他们更近期的版本是否实现了这一点吗?)。我认为,#pragma once
除了限制您选择替代编译器之外,几乎没什么用处。
#pragma once
不可靠,因为它存在不同文件层次结构(当前工作目录)、软连接、硬链接、网络文件系统甚至名称冲突(尝试使用名为string.h
的文件)等问题。如果不考虑速度,你可以编写一个脚本,将文件中的任何%INCLUDE_GUARD%
替换为自动管理的保护符号;你可以将头文件命名为header.hpp.in
,因为你已经有了预处理条件对,最终的头文件不会流动,并且编译器会在诊断信息中正确地输出行号。 - djsp#pragma once
可能存在的可靠性问题。如果我有两个不同的头文件foo/string.h
和bar/string.h
,那么使用#pragma once
意味着我可以将它们都包含一次,同时包含它们也没有问题。如果我使用 include guard,那么我可能会在两个文件中写类似于#ifndef STRING_H
的内容,这意味着我不能同时包含foo/string.h
和bar/string.h
。 - Brandinstring.h
,那么如果你没有使用命名空间,很可能会出现内部名称冲突,因为这两个文件都可能涉及字符串操作。如果你使用了命名空间,我认为你应该在包含保护标识符中包括命名空间。 - JAB