35

在 header/cpp 文件中应该以什么顺序声明 headers?显然,后续标头所需的那些标头应该更早,并且特定于类的标头应该在 cpp 范围内而不是标头范围内,但是是否有固定的顺序约定/最佳实践?

4

10 回答 10

64

在头文件中,您必须包含所有头文件才能使其可编译。并且不要忘记使用前向声明而不是某些标题。

在源文件中:

  • 对应的头文件
  • 必要的项目标题
  • 3rd 方库标题
  • 标准库头文件
  • 系统头文件

按照这个顺序,您将不会错过任何忘记自己包含库的头文件。

于 2009-03-05T10:58:06.903 回答
21

良好做法:每个 .h 文件都应该有一个 .cpp,其中首先包含该 .h,然后再包含其他任何内容。这证明任何 .h 文件都可以放在首位。

即使标头不需要实现,您也可以创建一个仅包含该 .h 文件的 .cpp 文件,而不包含其他任何内容。

这意味着您可以以任何您喜欢的方式回答您的问题。将它们包含在什么顺序中并不重要。

如需进一步的重要提示,请尝试这本书:Large-Scale C++ Software Design - 可惜它太贵了,但它实际上是 C++ 源代码布局的生存指南。

于 2009-03-05T10:56:17.500 回答
6

在头文件中,我倾向于首先放置标准头,然后是我自己的头(两个列表都按字母顺序排列)。在实现文件中,我首先放置相应的标头(如果有),然后是标准标头和其他依赖标头。

顺序无关紧要,除非您大量使用宏和#define; 在这种情况下,您必须检查您定义的宏是否不会替换先前包含的宏(当然,除非这是您想要的)。

关于此声明

后续标题需要的那些应该更早

标头不应该依赖于它之前包含的其他标头!如果它需要标题,它只包含它们。标头保护将防止多重包含:

#ifndef FOO_HEADER_H
#define FOO_HEADER_H
...
#endif

编辑

由于我写了这个答案,我改变了在我的代码中排序包含指令的方式。现在,我尝试始终将标头按标准化的递增顺序排列,因此我的项目的标头排在第一位,然后是 3rd 方库标头,然后是标准标头。

例如,如果我的一个文件使用我编写的库、Qt、Boost 和标准库,我将按如下方式订购包含:

//foo.cpp
#include "foo.hpp"

#include <my_library.hpp>
// other headers related to my_library

#include <QtCore/qalgorithms.h>
// other Qt headers

#include <boost/format.hpp> // Boost is arguably more standard than Qt
// other boost headers

#include <algorithms>
// other standard algorithms

我这样做的原因是为了检测我自己的标头中缺少的依赖项:让我们假设例如my_library.hpp使用std::copy,但不包括<algorithm>. 如果我在<algorithm>in之后包含它foo.cpp,那么这个缺失的依赖项将不会被注意到。相反,按照我刚才提出的命令,编译器会报错std::copy没有被声明,让我更正my_library.hpp.

在每个“库”组中,我尝试保持包含指令按字母顺序排列,以便更轻松地找到它们。

在旁注中,一个好的做法也是最大程度地限制头文件之间的依赖关系。文件应包含尽可能少的标头,尤其是标头文件。事实上,你包含的头文件越多,当某些事情发生变化时,需要重新编译的代码就越多。限制这些依赖关系的一个好方法是使用前向声明,这在头文件中通常就足够了(请参阅何时可以使用前向声明?)。

于 2009-03-05T11:00:24.307 回答
5

Google C++ 样式指南、名称和包含顺序

在 dir/foo.cc 中,其主要目的是实现或测试 dir2/foo2.h 中的内容,按如下顺序排列您的包含:

  • dir2/foo2.h(首选位置 - 请参阅下面的详细信息)。
  • C 系统文件。
  • C++ 系统文件。
  • 其他库的 .h 文件。
  • 您项目的 .h 文件。
于 2009-03-05T11:37:57.120 回答
3

我曾经按字母顺序排列它们(更容易找到)

于 2009-03-05T10:50:52.520 回答
2

“如何”并不明显,但“是什么”是显而易见的。您的目标是确保包含头文件的顺序不重要(我的意思是“从不!”)。

一个很好的帮助是在构建仅包含其中一个的 cpp 文件(每个头文件一个)时测试头文件是否编译。

于 2009-03-05T11:13:52.933 回答
1

对于 .cpp 文件,您应该包含类的标头或您首先要实现的任何内容,这样您就可以捕捉到该标头缺少某些包含的情况。之后,大多数编码指南倾向于首先包含系统标头,其次是项目标头,例如Google C++ Style Guide

于 2009-03-05T10:58:11.473 回答
0

这是一个依赖关系,很大程度上取决于您在我们的标题中放置的内容。一个事实是,您可能对此非常臭名昭著并最小化以保持您的包含严格,但您最终会遇到您想要使用包含防护的情况。

#ifndef MY_HEADER_H
#define MY_HEADER_H
//...
#endif

问题在开始时并不那么明显,但随着软件复杂性的增加,您的依赖项也会增加。您可以做得很好,并且对此很聪明,但是较大的 C++ 项目通常充满了包含。你可以尝试,但你只能做这么多。所以要勤奋,想想你的包括,是的!但是你肯定会在某些时候产生循环依赖,这就是你需要包含保护的原因。

于 2009-03-05T10:53:57.803 回答
0

如果一个标头需要其他标头,那么它只是将它们包含在该标头中。

尝试构造您的代码,以便您传递指针或引用并在可能的地方前向声明。

在实现中,应该首先列出定义它的标头(Visual Studio 除外,如果您使用的是 pch,则 stdafx 将首先出现)。

我通常根据需要列出它们。

于 2009-03-05T10:58:37.493 回答
-1

我发现以下约定最有用:

模块.cpp:

// this is the header used to trigger inclusion of precompiled headers
#include <precompiled.h> 
// this ensures that anything that includes "module.h" works
#include "module.h"
// other headers, usually system headers, the project

重要的是将模块的头文件作为第一个非预编译头文件。这确保了“module.h”没有意外的依赖。

如果您正在处理一个磁盘访问时间很慢的大型项目,我已经看到这种风格用于减少构建时间:

模块.cpp:

// this is the header used to trigger inclusion of precompiled headers
#include <precompiled.h> 
// this ensures that anything that includes "module.h" works
#include "module.h"
// other headers, usually system headers, the project
#if !defined _OTHER_MODULE_GUARD_
#include "other_module.h"
#endif 

#if !defined _ANOTHER_MODULE_GUARD_
#include "another_module.h"
#endif 

这有点冗长,但确实节省了磁盘查找,因为如果已经包含标题,则不会搜索/打开标题。如果没有保护检查,编译器将寻找并打开头文件,解析整个文件以最终#ifdef将整个文件取出。

于 2009-03-05T11:02:40.840 回答