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

注意:为了获得更快的编译时间,我们可以使用冗余包含守卫,但这增加了包含文件和包含文件之间的紧密耦合。通常这是可以的,因为守卫应该基于文件名,并且只在你需要改变包含名称时才会改变。


当前回答

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

#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两次,一次是原始版本,一次是模拟版本。

其他回答

对于那些想使用#pragma一次并将守卫包含在一起的人:如果你不使用MSVC,那么你不会从#pragma一次得到太多优化。

你不应该把“#pragma once”放在一个应该被包含多次的头文件中,因为每次包含可能会产生不同的效果。

下面是关于#pragma用法的详细讨论和示例。

我回答了一个相关的问题:

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

我把答案也加在这里,以防有人被这个问题绊倒,而不是其他问题。

直到#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,因为我的代码有时必须使用MSVC或GCC以外的东西进行编译(嵌入式系统的编译器并不总是有#pragma)。

所以我必须使用#include守卫。我也可以像一些回答建议的那样使用一次#pragma,但似乎没有太多的理由,而且它经常会在不支持它的编译器上引起不必要的警告。

我不确定这种务实会节省多少时间。我听说编译器通常已经识别出一个头文件中除了守卫宏之外的注释之外什么都没有,并且会在这种情况下执行#pragma。,不再处理该文件)。但我不确定这是真的还是编译器可以做这种优化。

在任何一种情况下,对我来说使用#include守卫更容易,它将在任何地方工作,而不用再担心它了。

#pragma一次允许编译器在再次出现该文件时完全跳过该文件——而不是解析该文件,直到它到达#include守卫。

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

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

当你限制你的平台,比如说“桌面上的主流编译器”,你可以安全地省略#include守卫,但我在这方面也感到不安。

OT:如果你有其他关于加速构建的技巧或经验可以分享,我很好奇。