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

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


当前回答

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

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

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

其他回答

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

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

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

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

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

如果你确信你永远不会在不支持它的编译器中使用这段代码(Windows/VS, GCC和Clang是支持它的编译器的例子),那么你当然可以使用#pragma一次而不用担心。

您也可以两者都使用(参见下面的示例),这样就可以在兼容系统上获得可移植性和编译加速

#pragma once
#ifndef _HEADER_H_
#define _HEADER_H_

...

#endif

我只是想补充一下这个讨论,我只是在VS和GCC上编译,并且习惯使用包含守卫。我现在已经切换到#pragma一次了,对我来说唯一的原因不是性能或可移植性或标准,因为我并不关心什么是标准,只要VS和GCC支持它,那就是:

#pragma一次性减少了出现错误的可能性。

将一个头文件复制并粘贴到另一个头文件,修改它以满足自己的需要,并且忘记更改include守卫的名称,这太容易了。一旦包含了两者,就需要花费一些时间来跟踪错误,因为错误消息并不一定是清晰的。

在Konrad Kleine的解释之上。

简要总结:

当我们使用# pragma一次时,编译器的责任很大,不允许它被包含多次。这意味着,在您在文件中提到代码片段之后,就不再是您的责任了。

现在,编译器在文件的开头寻找这个代码片段,并跳过它不被包含(如果已经包含一次)。这肯定会减少编译时间(在一般和大型系统中)。然而,在模拟/测试环境中,由于循环等依赖关系,将使测试用例的实现变得困难。

现在,当我们为头文件使用#ifndef XYZ_H时,维护头文件的依赖关系更多地是开发人员的责任。这意味着,每当由于一些新的头文件,有循环依赖的可能性,编译器只会在编译时标记一些“未定义的..”错误消息,由用户检查实体的逻辑连接/流程,并纠正不适当的包含。

这肯定会增加编译时间(因为需要纠正和重新运行)。此外,由于它是在包含文件的基础上工作的,基于“XYZ_H”定义状态,如果不能获得所有定义,仍然会报错。

因此,为了避免这种情况,我们应该使用,as;

#pragma once
#ifndef XYZ_H
#define XYZ_H
...
#endif

即两者的结合。