C / C ++包括头文件顺序

时间:2010-05-04 03:09:23

标签: c++ c

应该指定哪些顺序包含文件,即将一个标题包含在另一个标题之前的原因是什么?

例如,系统文件,STL和Boost是在本地包含文件之前还是之后进行的?

11 个答案:

答案 0 :(得分:251)

我不认为有推荐的订单,只要它编译!令人烦恼的是,当某些标题需要首先包含其他标题时...这是标题本身的问题,而不是包含的顺序。

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

  1. 与此cpp文件对应的文件(如果适用)
  2. 来自同一组件的标头
  3. 其他组件的标题
  4. 系统标题。
  5. 我的理由是,它应该证明每个标题(有一个cpp)可以是#include d而没有先决条件。其余的似乎从那里开始逻辑流动。

答案 1 :(得分:86)

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

“用C ++思考”特别提到了这一点,引用了Lakos的“大规模C ++软件设计”:

通过确保组件的.h文件自行解析,可以避免潜在的使用错误 - 没有外部提供的声明或定义......包含.h文件作为.c文件的第一行确保.h文件中没有缺少组件物理接口固有的关键信息(或者,如果有的话,只要您尝试编译.c文件就会发现它。)

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

  1. 此实现的原型/接口标头(即与.cpp / .cc文件对应的.h / .hh文件)。
  2. 根据需要来自同一项目的其他标题。
  3. 来自其他非标准非系统库的标题(例如,Qt,Eigen等)。
  4. 来自其他“近乎标准”的图书馆的标题(例如,Boost)
  5. 标准C ++标题(例如,iostream,functional等)
  6. 标准C标头(例如,cstdint,dirent.h等)
  7. 如果任何标题包含在此订单中的问题,请修复它们(如果您的)或不使用它们。阻止不编写干净标题的库。

    Google's C++ style guide认为几乎反过来,根本没有任何理由;我个人倾向于赞成Lakos方法。

答案 2 :(得分:47)

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

  1. 所有标题(以及任何源文件)都应包含所需内容。他们应该依赖他们的用户,包括事物。
  2. 作为一个附件,所有标题都应该包含警戒,这样就不会因为过于雄心勃勃地应用上述规则1而多次包含它们。
  3. 我也遵循以下准则:

    1. 首先包含系统标头(stdio.h等),并带有分界线。
    2. 逻辑分组。
    3. 换句话说:

      #include <stdio.h>
      #include <string.h>
      
      #include "btree.h"
      #include "collect_hash.h"
      #include "collect_arraylist.h"
      #include "globals.h"
      

      虽然是准则,但这是一个主观的事情。另一方面,我严格执行规则,甚至提供包含警卫的“包装”头文件,并且如果一些讨厌的第三方开发者没有订阅我的愿景,则分组包括: - )

答案 3 :(得分:19)

将我自己的砖添加到墙上。

  1. 每个标题都需要自给自足,只有在首次包含至少一次时才能进行测试
  2. 不应错误地通过引入符号(宏,类型等)来修改第三方标题的含义。
  3. 所以我通常会这样:

    // 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文件的标题(健全性检查)
    • 系统标题
    • 第三方标头,按依赖顺序组织
    • 项目标题
    • 投放私有标题

    另请注意,除了系统标头之外,每个文件都位于一个文件夹中,其名称为其命名空间,因为它更容易以这种方式跟踪它们。

答案 4 :(得分:14)

我很确定这在理智的世界中的任何地方都不是推荐的做法,但我喜欢将系统包括在文件名长度中,在相同长度内按字母顺序排序。像这样:

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

我认为在其他人之前包含你自己的标题是个好主意,以避免包含顺序依赖的耻辱。

答案 5 :(得分:13)

我建议:

  1. 您正在构建的.cc模块的标头。 (帮助确保项目中的每个标头都没有对项目中其他标头的隐式依赖。)
  2. C系统文件。
  3. C ++系统文件。
  4. 平台/ OS /其他头文件(例如win32,gtk,openGL)。
  5. 项目中的其他头文件。
  6. 当然,在可能的情况下,每个部分的字母顺序。

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

答案 6 :(得分:6)

这不是主观的。确保您的标题不依赖于#include d按特定顺序排列。您可以确定包含STL或Boost标头的顺序无关紧要。

答案 7 :(得分:3)

首先包含与.cpp相对应的标题...换句话说,source1.cpp在包含任何其他内容之前应包含source1.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

答案 8 :(得分:2)

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

使用预编译的头文件很复杂。解决此问题的一种方法是,在不使项目编译器特定的情况下,使用其中一个项目头作为预编译头文件包含文件。

答案 9 :(得分:1)

在决定特定的包含顺序时,几个单独的考虑因素被混为一谈。让我试着解开。

1.检查自足性

许多答案表明包含顺序应该作为检查您的标头是否自包含的检查。这混淆了测试编译

的考虑

您可以单独检查您的标题是否是自包含的。 “静态分析”独立于任何编译过程。例如,运行

gcc headerfile.h -fsyntax-only

测试您的头文件是否自包含可以很容易地编写/自动化。甚至您的 makefile 也可以做到这一点。

无意冒犯,但 Lakos 的书是 1996 年的,将这些不同的关注点放在一起对我来说听起来像是 90 年代风格的编程。话虽如此,有些生态系统(现在的 Windows 还是 90 年代的?)缺乏用于脚本化/自动化测试的工具。

2.可读性

另一个考虑因素是可读性。当您查找源文件时,您只想轻松查看包含的内容。为此,您的个人品味和偏好最重要,尽管通常您要么将它们从最具体到最不具体排序,要么反过来(我更喜欢后者)。

在每个组中,我通常只按字母顺序包含它们。

3.包含顺序重要吗?

如果您的头文件是自包含的,那么从技术上讲,包含顺序对于编译结果根本不重要

也就是说,除非您对代码有(有问题?)特定的设计选择,例如不会自动包含的必要宏定义。在这种情况下,您应该重新考虑您的程序设计,尽管它当然可能非常适合您。

答案 10 :(得分:0)

在C / C ++世界中,这是一个很难解决的问题。

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

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

当某些标题根据上面的标题更改其操作(通过检查#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" ,否则stddef.h中的offsetof行将声明宏重新定义错误。< / p>

我们祈祷在我们的职业生涯中不再遇到任何此类案件。