我正在开发一个已知只能在windows上运行并在Visual Studio下编译的代码库(它与excel紧密集成,所以它不会消失)。我想知道我是否应该使用传统的包含守卫或使用#pragma一次为我们的代码。我认为让编译器处理一次#pragma会产生更快的编译,并且在复制和粘贴时更不容易出错。它也稍微不那么丑陋;)
注意:为了获得更快的编译时间,我们可以使用冗余包含守卫,但这增加了包含文件和包含文件之间的紧密耦合。通常这是可以的,因为守卫应该基于文件名,并且只在你需要改变包含名称时才会改变。
我正在开发一个已知只能在windows上运行并在Visual Studio下编译的代码库(它与excel紧密集成,所以它不会消失)。我想知道我是否应该使用传统的包含守卫或使用#pragma一次为我们的代码。我认为让编译器处理一次#pragma会产生更快的编译,并且在复制和粘贴时更不容易出错。它也稍微不那么丑陋;)
注意:为了获得更快的编译时间,我们可以使用冗余包含守卫,但这增加了包含文件和包含文件之间的紧密耦合。通常这是可以的,因为守卫应该基于文件名,并且只在你需要改变包含名称时才会改变。
当前回答
直到#pragma成为标准的那一天(这不是未来标准的优先级),我建议你使用它并使用守卫,这样:
#ifndef BLAH_H
#define BLAH_H
#pragma once
// ...
#endif
原因如下:
#pragma once is not standard, so it is possible that some compiler don't provide the functionality. That said, all major compiler supports it. If a compiler don't know it, at least it will be ignored. As there is no standard behavior for #pragma once, you shouldn't assume that the behavior will be the same on all compiler. The guards will ensure at least that the basic assumption is the same for all compilers that at least implement the needed preprocessor instructions for guards. On most compilers, #pragma once will speed up compilation (of one cpp) because the compiler will not reopen the file containing this instruction. So having it in a file might help, or not, depending on the compiler. I heard g++ can do the same optimization when guards are detected but it have to be confirmed.
同时使用这两种编译器,可以充分利用每种编译器的优点。
现在,如果您没有一些自动脚本来生成守卫,那么只使用#pragma一次可能会更方便。要知道这对可移植代码意味着什么。(我使用VAssistX生成守卫和pragma一次快速)
你应该总是认为你的代码是可移植的(因为你不知道未来是由什么组成的),但如果你真的认为它不应该用另一个编译器编译(例如非常特定的嵌入式硬件的代码),那么你应该检查一下编译器文档中关于#pragma的内容,以了解你真正在做什么。
其他回答
直到#pragma成为标准的那一天(这不是未来标准的优先级),我建议你使用它并使用守卫,这样:
#ifndef BLAH_H
#define BLAH_H
#pragma once
// ...
#endif
原因如下:
#pragma once is not standard, so it is possible that some compiler don't provide the functionality. That said, all major compiler supports it. If a compiler don't know it, at least it will be ignored. As there is no standard behavior for #pragma once, you shouldn't assume that the behavior will be the same on all compiler. The guards will ensure at least that the basic assumption is the same for all compilers that at least implement the needed preprocessor instructions for guards. On most compilers, #pragma once will speed up compilation (of one cpp) because the compiler will not reopen the file containing this instruction. So having it in a file might help, or not, depending on the compiler. I heard g++ can do the same optimization when guards are detected but it have to be confirmed.
同时使用这两种编译器,可以充分利用每种编译器的优点。
现在,如果您没有一些自动脚本来生成守卫,那么只使用#pragma一次可能会更方便。要知道这对可移植代码意味着什么。(我使用VAssistX生成守卫和pragma一次快速)
你应该总是认为你的代码是可移植的(因为你不知道未来是由什么组成的),但如果你真的认为它不应该用另一个编译器编译(例如非常特定的嵌入式硬件的代码),那么你应该检查一下编译器文档中关于#pragma的内容,以了解你真正在做什么。
#pragma一次允许编译器在再次出现该文件时完全跳过该文件——而不是解析该文件,直到它到达#include守卫。
因此,语义略有不同,但如果它们按照预期的方式使用,则它们是相同的。
两者结合可能是最安全的方法,因为在最坏的情况下(编译器将未知的pragma标记为实际错误,而不仅仅是警告),你只需要删除#pragma本身。
当你限制你的平台,比如说“桌面上的主流编译器”,你可以安全地省略#include守卫,但我在这方面也感到不安。
OT:如果你有其他关于加速构建的技巧或经验可以分享,我很好奇。
我认为你应该做的第一件事是看看这是否真的会产生影响。您应该首先测试性能。谷歌上的一个搜索结果是这样的。
在结果页面中,对我来说,列是缓慢的,但很明显,至少到VC6,微软没有实现其他工具正在使用的包括保护优化。当include守卫是内部的时候,它花费的时间是外部守卫的50倍(外部包含守卫至少和#pragma一样好)。但让我们考虑一下这可能产生的影响:
根据给出的表格,打开include并检查它的时间是使用#pragma的等效时间的50倍。但在1999年,每个文件的实际时间是1微秒!
那么,一个TU会有多少重复的头?这取决于你的风格,但如果我们说平均每个TU有100个副本,那么在1999年,我们可能要为每个TU支付100微秒。随着HDD的改进,现在这个数字可能会显著降低,但即使是这样,通过预编译头文件和正确的依赖关系,跟踪一个项目的累计成本几乎肯定是你构建时间中微不足道的一部分。
现在,在另一方面,可能不太可能,如果你曾经移动到一个不支持#pragma的编译器,那么考虑一下要花多少时间来更新你的整个源代码库,以包含守卫而不是#pragma?
微软没有理由不能像GCC和其他编译器一样实现包含保护优化(实际上有人能确认他们的最新版本是否实现了这一点吗?)恕我直言,#pragma once除了限制你对可选编译器的选择外,几乎没有什么作用。
如果你确信你永远不会在不支持它的编译器中使用这段代码(Windows/VS, GCC和Clang是支持它的编译器的例子),那么你当然可以使用#pragma一次而不用担心。
您也可以两者都使用(参见下面的示例),这样就可以在兼容系统上获得可移植性和编译加速
#pragma once
#ifndef _HEADER_H_
#define _HEADER_H_
...
#endif
从软件测试人员的角度来看
#pragma once比include守卫更短,更不容易出错,被大多数编译器支持,有人说它编译更快(这不是真的[不再])。
但我仍然建议你使用标准的#ifndef include守卫。
为什么ifndef ?
考虑这样一个人为的类层次结构,其中每个类a、B和C都存在于自己的文件中:
a.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的行为。实现这一点的一种方法是使用例如谷歌mock编写一个模拟类,并将其放在目录mocks/b.h中。注意,类名没有改变,但它只存储在不同的目录中。但最重要的是,包含守护的名称与原始文件b.h中的名称完全相同。
mocks / 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/放在编译器的include路径中。
为什么不能用#pragma一次性完成?
如果你只使用了一次#pragma,你就会得到一个名称冲突,因为它不能防止你定义类B两次,一次是原始版本,一次是模拟版本。