在 header/cpp 文件中应该以什么顺序声明 headers?显然,后续标头所需的那些标头应该更早,并且特定于类的标头应该在 cpp 范围内而不是标头范围内,但是是否有固定的顺序约定/最佳实践?
10 回答
在头文件中,您必须包含所有头文件才能使其可编译。并且不要忘记使用前向声明而不是某些标题。
在源文件中:
- 对应的头文件
- 必要的项目标题
- 3rd 方库标题
- 标准库头文件
- 系统头文件
按照这个顺序,您将不会错过任何忘记自己包含库的头文件。
良好做法:每个 .h 文件都应该有一个 .cpp,其中首先包含该 .h,然后再包含其他任何内容。这证明任何 .h 文件都可以放在首位。
即使标头不需要实现,您也可以创建一个仅包含该 .h 文件的 .cpp 文件,而不包含其他任何内容。
这意味着您可以以任何您喜欢的方式回答您的问题。将它们包含在什么顺序中并不重要。
如需进一步的重要提示,请尝试这本书:Large-Scale C++ Software Design - 可惜它太贵了,但它实际上是 C++ 源代码布局的生存指南。
在头文件中,我倾向于首先放置标准头,然后是我自己的头(两个列表都按字母顺序排列)。在实现文件中,我首先放置相应的标头(如果有),然后是标准标头和其他依赖标头。
顺序无关紧要,除非您大量使用宏和#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
.
在每个“库”组中,我尝试保持包含指令按字母顺序排列,以便更轻松地找到它们。
在旁注中,一个好的做法也是最大程度地限制头文件之间的依赖关系。文件应包含尽可能少的标头,尤其是标头文件。事实上,你包含的头文件越多,当某些事情发生变化时,需要重新编译的代码就越多。限制这些依赖关系的一个好方法是使用前向声明,这在头文件中通常就足够了(请参阅何时可以使用前向声明?)。
在 dir/foo.cc 中,其主要目的是实现或测试 dir2/foo2.h 中的内容,按如下顺序排列您的包含:
- dir2/foo2.h(首选位置 - 请参阅下面的详细信息)。
- C 系统文件。
- C++ 系统文件。
- 其他库的 .h 文件。
- 您项目的 .h 文件。
我曾经按字母顺序排列它们(更容易找到)
“如何”并不明显,但“是什么”是显而易见的。您的目标是确保包含头文件的顺序不重要(我的意思是“从不!”)。
一个很好的帮助是在构建仅包含其中一个的 cpp 文件(每个头文件一个)时测试头文件是否编译。
对于 .cpp 文件,您应该包含类的标头或您首先要实现的任何内容,这样您就可以捕捉到该标头缺少某些包含的情况。之后,大多数编码指南倾向于首先包含系统标头,其次是项目标头,例如Google C++ Style Guide。
这是一个依赖关系,很大程度上取决于您在我们的标题中放置的内容。一个事实是,您可能对此非常臭名昭著并最小化以保持您的包含严格,但您最终会遇到您想要使用包含防护的情况。
#ifndef MY_HEADER_H
#define MY_HEADER_H
//...
#endif
问题在开始时并不那么明显,但随着软件复杂性的增加,您的依赖项也会增加。您可以做得很好,并且对此很聪明,但是较大的 C++ 项目通常充满了包含。你可以尝试,但你只能做这么多。所以要勤奋,想想你的包括,是的!但是你肯定会在某些时候产生循环依赖,这就是你需要包含保护的原因。
如果一个标头需要其他标头,那么它只是将它们包含在该标头中。
尝试构造您的代码,以便您传递指针或引用并在可能的地方前向声明。
在实现中,应该首先列出定义它的标头(Visual Studio 除外,如果您使用的是 pch,则 stdafx 将首先出现)。
我通常根据需要列出它们。
我发现以下约定最有用:
模块.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
将整个文件取出。