C / C ++包含头文件的顺序


287

应该指定包含文件的顺序,即,为什么在一个头之前包含一个头的原因是什么?

例如,系统文件,STL和Boost是否在本地包含文件之前或之后?


2
下面的大量答案是为什么Java开发人员决定使用单独的标头。:-)但是,确实有一些很好的答案,尤其是要确保自己的头文件可以独立存在。
克里斯·K

37
我喜欢这样的问题,即那些票数超过100且对于很多人来说非常有趣的问题如何被封闭为“非建设性的”。
Andreas


3
@mrt,因此强烈提醒纳粹汤社区:要么您遵循一些非常严格的规则,要么“对您没有适当的答案/评论!”。但是,如果有人遇到与编程有关的任何问题,(通常)这是第一个访问的站点
。– Imago

Answers:


289

我认为没有推荐的顺序,只要可以编译即可!令人讨厌的是,当某些标头要求首先包含其他标头时...这是标头本身的问题,而不是包含顺序的问题。

我个人的喜好是从本地到全局,每个小节都按字母顺序排列,即:

  1. 与此cpp文件对应的h文件(如果适用)
  2. 来自同一组件的标题
  3. 其他组件的标题
  4. 系统标题。

我对1.的理由是,它应证明每个标头(为此有一个cpp)都可以在#include没有先决条件的情况下使用d(terminus technicus:标头是“独立的”)。其余的似乎只是从逻辑上流向那里。


16
与您几乎一样,除了我从全局转到本地,并且与源文件相对应的标头未得到特殊处理。
乔恩·普迪

127
@Jon:我要说的恰恰相反!:-)我认为您的方法可能会引入隐藏的依赖关系,例如,如果myclass.cpp包含<string>然后是<myclass.h>,则无法在构建时发现myclass.h本身可能依赖于字符串;因此,如果以后您或其他人包括myclass.h但不需要字符串,则会收到需要在cpp或标头本身中修复的错误。但是我很想知道人们认为从长远来看,这是否会更好……您为什么不提出提案的答案,而我们会看到谁“获胜”呢?;-)
squelart'5

3
我这次使用的是Dave Abrahams的推荐,它是针对一般订购的。他指出,与@squelart一样,说明从本地到更一般的来源中都包含缺少的标头的原因。重要的关键是,与第三方和系统库相比,您更容易犯这些错误。
GrafikRobot 2010年

7
@PaulJansen这是一个不好的做法,最好使用一种更容易崩溃的技术,以便可以纠正该不好的做法而不是将其隐藏起来。本地到全球FTW
bames53 2013年

10
@PaulJansen是的,我指的是否决标准行为。它可能是偶然发生的,例如,打破ODR可能是偶然发生的。解决方案不是使用在此类事故发生时隐藏起来的做法,而是使用最有可能使他们大声爆炸的做法,以便可以尽早发现并纠正错误。
bames53 2013年

105

要记住的一件大事是,您的标头不应依赖于其他标头被首先包含。确保这一点的一种方法是在其他任何标题之前包含标题。

“使用C ++进行思考”特别提到了Lakos的“大型C ++软件设计”:

通过确保组件的.h文件本身可以进行解析-无需外部提供的声明或定义,可以避免潜在的使用错误。包括.h文件作为.c文件的第一行,可以确保没有关键内容.h文件中缺少组件物理接口固有的信息(或者,如果存在,则在尝试编译.c文件时会立即找到它)。

也就是说,按以下顺序包括:

  1. 此实现的原型/接口标头(即与此.cpp / .cc文件相对应的.h / .hh文件)。
  2. 根据需要来自同一项目的其他标头。
  3. 来自其他非标准,非系统库(例如Qt,Eigen等)的标头。
  4. 来自其他“几乎标准”库的标题(例如,Boost)
  5. 标准C ++标头(例如,iostream,function等)
  6. 标准C标头(例如cstdint,dirent.h等)

如果任何标题在按此顺序包含时有问题,请修复它们(如果使用的话)或不使用它们。抵制不写干净标题的库。

Google的C ++风格指南提出了几乎相反的说法,实际上根本没有道理。我个人倾向于拉科斯的方法。


13
到目前为止,根据Lakos的建议,Google C ++风格指南建议首先包含相关的头文件。
FilipBártek16年

除了第一个相关的标头之外,您将获得太多的好处,因为一旦开始包含项目中的标头,您将引入许多系统依赖性。
米卡

@Micah-引入“很多系统依赖项”的项目内标头是错误的设计,正是我们在此试图避免的。关键是要避免不必要的包含和未解决的依赖关系。应该可以包含所有标头,而不必首先包含其他标头。如果项目中的头文件需要系统依赖性,那么就可以了-然后,您(也不应该)在其后包括系统依赖性,除非该文件本地的代码使用该系统dep中的内容。您不能也不应该依赖标头(甚至您的标头)来包括您使用的系统dep。
内森·保罗·西蒙斯

49

我遵循两个简单的规则来避免绝大多数问题:

  1. 所有的头(实际上任何的源文件)应该包括他们需要什么。他们应该依赖于他们的用户,包括东西。
  2. 作为辅助,所有标头都应包含防护,以免由于过度使用上述规则1而过分包含它们。

我还遵循以下准则:

  1. 首先用分隔线包括系统头(stdio.h等)。
  2. 对其进行逻辑分组。

换一种说法:

#include <stdio.h>
#include <string.h>

#include "btree.h"
#include "collect_hash.h"
#include "collect_arraylist.h"
#include "globals.h"

虽然作为准则,但这是主观的。另一方面,我严格执行这些规则,甚至在某些令人讨厌的第三方开发人员不认同我的愿景的情况下,甚至为“包装”头文件提供包含保护和分组包含的观点:-)


6
+1“所有标头(以及实际上任何源文件)都应包含所需的内容。它们不应依赖于用户,包括事物。” 然而,如此多的人依赖于这种隐式包含行为,例如,使用NULL并且不包含<cstddef>。尝试移植此代码并在NULL上出现编译错误时非常烦人(我现在仅使用0的原因之一)。
stinky472 2010年

20
为什么首先包含系统标头?另一个为什么会更好,因为您的第一条规则。
jhasse

如果您使用功能测试宏,则第一个包含的宏可能不应该是标准库头。因此,从总体上讲,我认为“本地优先,全球之后”的政策是最好的。
hmijail哀悼辞职者,2017年

1
关于您的“不依赖用户”的第一个建议,在头文件中不需要包含头文件的前向声明呢?我们是否仍应包括头文件,因为前向声明使头文件的用户承担了包括适当文件的责任。
Zoso

22

在墙上添加我自己的砖块。

  1. 每个标头都必须是自给自足的,只有在至少包含一次它的情况下,才能对其进行测试
  2. 请勿通过引入符号(宏,类型等)来错误地修改第三方标头的含义

所以我通常是这样的:

// myproject/src/example.cpp
#include "myproject/example.h"

#include <algorithm>
#include <set>
#include <vector>

#include <3rdparty/foo.h>
#include <3rdparty/bar.h>

#include "myproject/another.h"
#include "myproject/specific/bla.h"

#include "detail/impl.h"

每一组与下一组用空白行分隔:

  • 首先对应于此cpp文件的标题(完整性检查)
  • 系统标题
  • 第三方标头,按依存关系顺序组织
  • 项目标题
  • 项目专用标头

还要注意,除了系统头文件之外,每个文件都位于一个具有名称空间名称的文件夹中,这是因为这样更容易跟踪它们。


2
这样其他头文件就不会受到它们的影响。这些系统标头定义的内容(X包含和Windows包含都不#define利于弄乱其他代码),并防止隐式依赖。例如,如果我们的代码库头文件foo.h确实依赖<map>.cc文件,但它在文件中使用的所有地方都<map>已经包含在内,我们可能不会注意到。直到有人试图包含foo.h而不首先包含<map>。然后他们会很生气。

@ 0A0D:第二个问题在此顺序上不是问题,因为每个.h问题至少都有一个.cpp首先包含它(实际上,在我的个人代码中,关联的单元测试首先包含它,而源代码将其包含在其合法组中) )。关于不受影响,如果任何头文件包括<map>在内,那么以后包含的所有头文件都会受到影响,因此对我来说似乎是一场失败的战斗。
Matthieu M.

1
当然,这就是为什么我会定期四处修正更旧的代码(甚至是较新的代码)的原因,而这需要不必要的包含,因为这只会增加构建时间。

@MatthieuM。我想知道你的论点背后的理由Header corresponding to this cpp file first (sanity check)。如果#include "myproject/example.h"将所有内容移到所有内容的末尾,有什么特别的地方吗?
MNS

1
@MNS:标头应该是独立的,也就是说,不要求在标头之前包含任何其他标头。作为标头的作者,您有责任确保做到这一点,最好的方法是拥有一个其中首先包含此标头的源文件。使用与头文件相对应的源文件很容易,另一个不错的选择是使用与头文件相对应的单元测试源文件,但是通用性较差(可能没有单元测试)。
Matthieu M.

16

我建议:

  1. 您正在构建的.cc模块的头。(有助于确保项目中的每个标头都没有对项目中其他标头的隐式依赖。)
  2. C系统文件。
  3. C ++系统文件。
  4. 平台/操作系统/其他头文件(例如win32,gtk,openGL)。
  5. 项目中的其他头文件。

当然,如有可能,请在每个部分中按字母顺序排列。

始终使用前向声明以避免#include头文件中不必要的。


+1,但为什么要按字母顺序?似乎可以使您感觉更好的东西,但没有实际好处。

9
字母顺序是任意顺序,但很容易。您不必按字母顺序进行操作,但必须选择一些顺序,以便每个人都能一致地进行操作。我发现它有助于避免重复并简化合并。而且,如果您使用崇高的文字,F5会为您订购。
i_am_jorf 2014年

14

我敢肯定,在理智的世界中,在任何地方都不推荐这样做,但是我希望按名称长度将系统包括在内,并按相同的长度按词法排序。像这样:

#include <set>
#include <vector>
#include <algorithm>
#include <functional>

我认为,在其他人之前包含您自己的标头是一个好主意,以避免包含顺序依赖的可耻性。


3
我喜欢通过使用由第二个,第三个然后第一个字母组成的键对标题进行排序:-)因此,矢量,集合,算法,函数对于您的示例都是如此。
paxdiablo

@paxdiablo,感谢您的提示。我正在考虑使用它,但我担心它可能最终会使文件名不稳定并可能翻倒。谁知道如果发生这种情况可能会包括什么-甚至可能windows.h
clstrfsck,2010年

40
长度排序?疯狂!
James McNellis'5

1
首先是+1。实际上,这很有意义,如果您需要用肉眼在视觉上定位文件头,则比按字母顺序好得多。
库格尔2010年

6

这不是主观的。确保标头不依赖#include于特定顺序的d。您可以确定,添加STL或Boost头的顺序并不重要。


1
我假设没有隐含的依赖关系
Anycorn

是的,但是编译器无法做出此假设,因此在编译之前,#include <A>,<B>永远不会与#include <B>,<A>相同。
米哈伊尔

4

首先包含与.cpp ...对应的标题,换句话说,source1.cppsource1.h在包含其他任何内容之前包含。我能想到的唯一例外是将MSVC与预编译头一起使用时,在这种情况下,您不得不stdafx.h其他内容。

推理:source1.h在所有其他文件之前包含之前,确保了它可以独立存在而没有依赖性。如果以后source1.h需要依赖,编译器会立即提醒您将必需的前向声明添加到中source1.h。反过来,这确保了标头可以被其依赖者以任何顺序包括在内。

例:

source1.h

class Class1 {
    Class2 c2;    // a dependency which has not been forward declared
};

source1.cpp

#include "source1.h"    // now compiler will alert you saying that Class2 is undefined
                    // so you can forward declare Class2 within source1.h
...

MSVC用户:我强烈建议使用预编译头。因此,将#include标准标头(以及其他永远不会更改的标头)的所有伪指令移动到stdafx.h


2

从最具体到最不具体,从.cpp的相应.hpp开始(如果存在)。这样,将显示头文件中任何不自足的隐藏依赖项。

使用预编译的标头会使情况变得复杂。解决此问题的一种方法是,不使项目特定于编译器,而是使用项目头之一作为预编译头包含文件。


1

在C / C ++世界中,这是一个棘手的问题,它包含许多超出标准的元素。

我认为只要编译头文件顺序就不会是一个严重的问题,就像squelart所说的那样。

我的想法是:如果所有这些标头中都没有符号冲突,则任何顺序都可以,并且标头依赖性问题可以在以后通过向有缺陷的.h中添加#include行来解决。

当某些标头根据上面的标头更改其动作(通过检查#if条件)时,才出现真正的麻烦。

例如,在VS2005的stddef.h中,有:

#ifdef  _WIN64
#define offsetof(s,m)   (size_t)( (ptrdiff_t)&(((s *)0)->m) )
#else
#define offsetof(s,m)   (size_t)&(((s *)0)->m)
#endif

现在的问题是:如果我有一个需要与许多编译器一起使用的自定义标头(“ custom.h”),包括一些未offsetof在其系统标头中提供的较旧的编译器,则应在标头中编写:

#ifndef offsetof
#define offsetof(s,m)   (size_t)&(((s *)0)->m)
#endif

而且一定要在用户告诉#include "custom.h" 以后的所有系统头,否则,线offsetof在STDDEF.H将断言宏重新定义错误。

我们祈祷在职业生涯中不再遇到此类情况。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.