Mat*_*ice 343 c++ coding-style
我正在研究一个已知只能在Windows上运行并在Visual Studio下编译的代码库(它与excel紧密集成,所以它不会去任何地方).我想知道我是否应该使用传统的包含警卫或#pragma once用于我们的代码.我认为让编译器处理#pragma once会产生更快的编译,并且在复制和粘贴时不易出错.它也稍微不那么难看;)
注意:为了获得更快的编译时间,我们可以使用Redundant Include Guards,但这会在包含的文件和包含文件之间增加紧密耦合.通常它没关系,因为防护应该基于文件名,并且只有在你需要改变包含名称时才会改变.
Bri*_*ndy 298
我不认为它会在编译时间上产生显着差异,但是#pragma once在编译器中得到了很好的支持,但实际上并不是标准的一部分.预处理器可能会更快一些,因为它更容易理解您的确切意图.
#pragma once 不太容易犯错误,输入的代码也少.
为了加快编译时间,只需向前声明而不是包含在.h文件中.
我更喜欢使用#pragma once.
请参阅此维基百科文章,了解使用两者的可能性.
Coo*_*kie 168
我只是想在这个讨论中加入我只是在VS和GCC上编译,并且习惯使用包含警戒.我现在已切换到#pragma once,我唯一的原因不是性能或可移植性或标准,因为只要VS和GCC支持它,我就不在乎标准是什么,那就是:
#pragma once 减少错误的可能性.
将头文件复制并粘贴到另一个头文件,修改它以满足需要,并忘记更改包含保护的名称是非常容易的.一旦包含两者,您需要一段时间来追踪错误,因为错误消息不一定清楚.
zwo*_*wol 109
#pragma once有无法修复的错误.永远不应该使用它.如果你的#include搜索路径足够复杂,编译器可能无法告诉两个头之间的区别与相同基(如a/foo.h和b/foo.h),所以#pragma once他们在一个将抑制两种.它也可能无法分辨两个不同的相对包含(例如,#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开发人员确实付出了相当大的努力以使其#pragma once尽可能可靠;请参阅GCC错误报告11569.但是,当前版本的GCC中的实现仍然可能失败条件,例如构建农场遭受时钟偏差.我不知道其他编译器的实现是什么样的,但我不希望任何人做得更好.)
Kla*_*aim 35
直到这一天#pragma once成为标准(目前不是未来标准的优先级),我建议你使用它并使用警卫,这样:
#ifndef BLAH_H
#define BLAH_H
#pragma once
// ...
#endif
Run Code Online (Sandbox Code Playgroud)
原因是:
#pragma once不是标准的,因此某些编译器可能不提供该功能.也就是说,所有主流编译器都支持它.如果编译器不知道它,至少它将被忽略.#pragma once,您不应该假设所有编译器的行为都是相同的.警卫将至少确保所有编译器的基本假设是相同的,至少为警卫实施所需的预处理器指令.#pragma once将加速编译(一个cpp),因为编译器不会重新打开包含该指令的文件.因此,将其置于文件中可能有所帮助,具体取决于编译器.我听说g ++可以在检测到防护时进行相同的优化,但必须进行确认.将两者结合使用,您可以获得每个编译器的最佳效果.
现在,如果你没有一些自动脚本来生成警卫,那么使用它可能会更方便#pragma once.只知道这对便携式代码意味着什么.(我正在使用VAssistX快速生成警卫和编译指示)
您应该几乎总是以可移植的方式思考您的代码(因为您不知道将来会做什么)但是如果您真的认为它不是要用其他编译器编译(例如,非常特定的嵌入式硬件的代码)那么你应该检查你的编译器文档#pragma once,了解你真正在做什么.
Kon*_*ine 32
#pragma once 比大多数编译器支持的包含保护更短,容易出错,并且有些人说它编译速度更快(不再那么真实).
但是我仍然建议你选择标准#ifndef包含警卫.
#ifndef?考虑这样一个人为的类层次结构,其中每个班的A,B和C住自己的文件中:
#ifndef A_H
#define A_H
class A {
public:
// some virtual functions
};
#endif
Run Code Online (Sandbox Code Playgroud)
#ifndef B_H
#define B_H
#include "a.h"
class B : public A {
public:
// some functions
};
#endif
Run Code Online (Sandbox Code Playgroud)
#ifndef C_H
#define C_H
#include "b.h"
class C : public B {
public:
// some functions
};
#endif
Run Code Online (Sandbox Code Playgroud)
现在让我们假设您正在为您的类编写测试,并且您需要模拟真正复杂类的行为B.一种方法是使用例如google mock编写一个mock类并将其放在一个目录中.请注意,类名称没有更改,但它只存储在不同的目录中.但最重要的是包含警卫的名称与原始文件中的名称完全相同.mocks/b.hb.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
Run Code Online (Sandbox Code Playgroud)
使用这种方法,您可以模拟类的行为,B而无需触及原始类或告诉C它.您所要做的就是将目录mocks/放在编译器的包含路径中.
#pragma once呢?如果你曾经使用过#pragma once,你会得到一个名字冲突,因为它不能保护你不要B两次定义类,一次是原始类,一次是模拟版.
Don*_*oer 23
如果你是积极的,你永远不会在不支持它(在Windows/VS,GCC,以及锵是编译器的例子编译器使用此代码不支持它),那么你当然可以使用#pragma一次无后顾之忧.
您也可以同时使用它们(参见下面的示例),以便在兼容系统上获得可移植性和编译加速
#pragma once
#ifndef _HEADER_H_
#define _HEADER_H_
...
#endif
Run Code Online (Sandbox Code Playgroud)
flu*_*ffy 19
在进行了关于假设#pragma once与#ifndef保护之间的假设性能权衡与正确与否的论证的延伸讨论之后(我#pragma once基于最近的一些灌输为基础),我决定最终测试#pragma once更快的理论因为编译器不必尝试重新#include创建已包含的文件.
为了测试,我自动生成了500个具有复杂相互依赖性的头文件,并且有一个.c文件#include全部都是.我用三种方式进行了测试,一次只用#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;
}
Run Code Online (Sandbox Code Playgroud)
现在,我的各种测试运行:
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
Run Code Online (Sandbox Code Playgroud)
正如您所看到的那样,#pragma once预处理的版本确实比#ifndef仅使用它的版本略快一些,但差异可以忽略不计,并且会远远超过实际构建和链接代码所花费的时间.也许有足够大的代码库可能会导致几秒钟的构建时间不同,但现代编译器之间能够优化#ifndef防护,操作系统具有良好的磁盘缓存,以及存储技术的速度越来越快,似乎性能论证没有实际意义,至少在这个时代的典型开发者系统上是这样.较旧且更具异国情调的构建环境(例如,托管在网络共享上的标头,从磁带构建等)可能会稍微改变等式,但在这些情况下,首先简单地构建不太脆弱的构建环境似乎更有用.
问题的事实是,#ifndef标准行为是标准化的,而#pragma once不是,并且#ifndef还处理奇怪的文件系统和搜索路径#pragma once极端情况,但是可能会被某些事情弄得很困惑,导致程序员无法控制的错误行为.主要的问题#ifndef是程序员为他们的警卫选择坏名称(名称冲突等),即使这样,API的消费者也很可能使用#undef- 不是一个完美的解决方案来覆盖那些可怜的名字,但这是可能的,#pragma once如果编译器错误地剔除了一个,则没有追索权#include.
因此,即使 #pragma once明显(稍微)更快,我也不同意这本身就是将其用于#ifndef警卫的理由.
编辑:感谢来自@LightnessRacesInOrbit的反馈,我增加了头文件的数量,并将测试更改为仅运行预处理器步骤,消除了编译和链接过程中添加的任何少量时间(这在以前是微不足道的现在不存在).正如所料,差异大致相同.
Mic*_*urr 15
我通常不打扰,#pragma once因为我的代码有时必须用MSVC或GCC以外的东西进行编译(嵌入式系统的编译器并不总是有#pragma).
所以我不得不使用#include guards.我也可以使用#pragma once一些答案建议,但似乎没有太多理由,它往往会导致不支持它的编译器发出不必要的警告.
我不确定pragma可能会节省多少时间.我听说编译器通常已经识别出标题什么时候除了保护宏之外什么都没有,并且#pragma once在这种情况下会做同等的事情(即,永远不再处理文件).但我不确定它是否属实,或者仅仅是编译器可以进行优化的情况.
无论哪种情况,我都可以更轻松地使用#include警卫,它可以在任何地方使用,而不用担心它.
我认为你应该做的第一件事是检查这是否真的会产生影响,即.你应该首先测试性能.其中一个谷歌搜索引发了这一点.
在结果页面中,对于我来说,这些列是关闭的,但很明显,至少VC6 microsoft没有实现其他工具正在使用的包含保护优化.如果包括后卫是内部的,那么包括后卫在外部的时间要长50倍(外部包括后卫至少和#pragma一样好).但是让我们考虑一下这可能产生的影响:
根据提供的表格,打开包含和检查它的时间是#pragma等价物的50倍.但实际这样做的时间是在1999年以每张1微秒的速度测量的!
那么,单个TU有多少个重复的标题?这取决于你的风格,但如果我们说平均TU有100个重复,那么在1999年我们可能每个TU支付100微秒.随着HDD的改进,这可能会显着降低,但即使这样,使用预编译的头文件和正确的依赖关系跟踪,项目的总累积成本几乎肯定是构建时间的一个重要部分.
现在,另一方面,尽管可能不太可能,但是如果您转移到不支持的编译器,#pragma once那么请考虑将整个源代码库更新为包含防护而不是#pragma需要多长时间?
没有理由微软无法以与GCC和其他所有编译器相同的方式实现包含保护优化(实际上任何人都可以确认他们的更新版本是否实现了这一点?).恕我直言,#pragma once除了限制你选择的替代编译器之外,做的很少.