我正在开发一个已知只能在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或GCC以外的东西进行编译(嵌入式系统的编译器并不总是有#pragma)。

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

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

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

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

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

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

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

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

在Konrad Kleine的解释之上。

简要总结:

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

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

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

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

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

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

即两者的结合。

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

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

#pragma once
#ifndef _HEADER_H_
#define _HEADER_H_

...

#endif

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

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

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