我正在开发一个已知只能在windows上运行并在Visual Studio下编译的代码库(它与excel紧密集成,所以它不会消失)。我想知道我是否应该使用传统的包含守卫或使用#pragma一次为我们的代码。我认为让编译器处理一次#pragma会产生更快的编译,并且在复制和粘贴时更不容易出错。它也稍微不那么丑陋;)
注意:为了获得更快的编译时间,我们可以使用冗余包含守卫,但这增加了包含文件和包含文件之间的紧密耦合。通常这是可以的,因为守卫应该基于文件名,并且只在你需要改变包含名称时才会改变。
我正在开发一个已知只能在windows上运行并在Visual Studio下编译的代码库(它与excel紧密集成,所以它不会消失)。我想知道我是否应该使用传统的包含守卫或使用#pragma一次为我们的代码。我认为让编译器处理一次#pragma会产生更快的编译,并且在复制和粘贴时更不容易出错。它也稍微不那么丑陋;)
注意:为了获得更快的编译时间,我们可以使用冗余包含守卫,但这增加了包含文件和包含文件之间的紧密耦合。通常这是可以的,因为守卫应该基于文件名,并且只在你需要改变包含名称时才会改变。
当前回答
对于那些想使用#pragma一次并将守卫包含在一起的人:如果你不使用MSVC,那么你不会从#pragma一次得到太多优化。
你不应该把“#pragma once”放在一个应该被包含多次的头文件中,因为每次包含可能会产生不同的效果。
下面是关于#pragma用法的详细讨论和示例。
其他回答
#pragma一次允许编译器在再次出现该文件时完全跳过该文件——而不是解析该文件,直到它到达#include守卫。
因此,语义略有不同,但如果它们按照预期的方式使用,则它们是相同的。
两者结合可能是最安全的方法,因为在最坏的情况下(编译器将未知的pragma标记为实际错误,而不仅仅是警告),你只需要删除#pragma本身。
当你限制你的平台,比如说“桌面上的主流编译器”,你可以安全地省略#include守卫,但我在这方面也感到不安。
OT:如果你有其他关于加速构建的技巧或经验可以分享,我很好奇。
对于那些想使用#pragma一次并将守卫包含在一起的人:如果你不使用MSVC,那么你不会从#pragma一次得到太多优化。
你不应该把“#pragma once”放在一个应该被包含多次的头文件中,因为每次包含可能会产生不同的效果。
下面是关于#pragma用法的详细讨论和示例。
#pragma曾经有无法修复的错误。它不应该被使用。
如果你的#include搜索路径足够复杂,编译器可能无法区分两个具有相同基底名的头文件(例如a/foo.h和b/foo.h),因此在其中一个头文件中使用#pragma一次将同时屏蔽两个头文件。它也可能无法区分两个不同的相对include(例如#include "foo.h"和#include "../a/foo.h"指的是同一个文件,因此#pragma once将无法在应该包含冗余include的情况下抑制冗余include。
这也会影响编译器避免使用#ifndef守卫重读文件的能力,但这只是一种优化。使用#ifndef守卫,编译器可以安全地读取它不确定已经看过的任何文件;如果是错的,它只需要做一些额外的工作。只要没有两个头文件定义相同的保护宏,代码将按预期编译。如果两个头文件定义了相同的保护宏,程序员可以进去修改其中一个。
#pragma曾经没有这样的安全网——如果编译器对头文件的标识错误,无论哪种情况,程序都将编译失败。如果你遇到了这个错误,你唯一的选择是停止使用#pragma一次,或者重命名一个头文件。头文件的名称是API契约的一部分,因此重命名可能不是一个选项。
(简而言之,这是不可修复的,因为Unix和Windows文件系统API都没有提供任何机制来保证告诉你两个绝对路径名是否指向同一个文件。如果您认为inode编号可以用于此,那么对不起,您错了。)
(历史注:12年前,当我有权从GCC中删除#pragma和#import时,我没有这样做的唯一原因是苹果的系统头文件依赖于它们。现在回想起来,这不应该阻止我。)
(因为这个问题已经在评论中出现了两次:GCC开发人员确实花了很多精力让#pragma once尽可能可靠;参见GCC bug报告11569。然而,GCC当前版本中的实现仍然可能在合理的条件下失败,例如构建农场受到时钟倾斜的影响。我不知道其他编译器的实现是什么样的,但我不期望任何人做得更好。)
如果你确信你永远不会在不支持它的编译器中使用这段代码(Windows/VS, GCC和Clang是支持它的编译器的例子),那么你当然可以使用#pragma一次而不用担心。
您也可以两者都使用(参见下面的示例),这样就可以在兼容系统上获得可移植性和编译加速
#pragma once
#ifndef _HEADER_H_
#define _HEADER_H_
...
#endif
我只是想补充一下这个讨论,我只是在VS和GCC上编译,并且习惯使用包含守卫。我现在已经切换到#pragma一次了,对我来说唯一的原因不是性能或可移植性或标准,因为我并不关心什么是标准,只要VS和GCC支持它,那就是:
#pragma一次性减少了出现错误的可能性。
将一个头文件复制并粘贴到另一个头文件,修改它以满足自己的需要,并且忘记更改include守卫的名称,这太容易了。一旦包含了两者,就需要花费一些时间来跟踪错误,因为错误消息并不一定是清晰的。