包含头文件的最佳实践

3

我想知道在include文件中直接包含include语句和在源文件中包含include语句有什么优缺点。

个人而言,我喜欢让我的include文件“干净”,这样当我将它们包含到某个c / cpp文件中时,我不必寻找每个可能需要的标头,因为include文件本身会处理。另一方面,如果我在include文件中包含includes,编译时间可能会更长,因为即使使用了include保护,文件也必须首先被解析。这只是品味问题,还是其中一个方面有优缺点?

我的意思是:

sample.h

#ifdef ...

#include "my_needed_file.h"
#include ...

class myclass
{
}

#endif

sample.c

#include "sample.h"

my code goes here

Versus:

sample.h

#ifdef ...

class myclass
{
}

#endif

sample.c

#include "my_needed_file.h"
#include ...
#include "sample.h"

my code goes here

2
祝你在C语言中编译类成功。 - cHao
每一个include都会增加编译时间并带来更多的标识符。你需要限制它们。如果不需要在sample.h中包含其他翻译单元.cpp才能正常工作,请不要将其添加到sample.h中。如果认为翻译时间并不重要,那么你还没有尝试过模板重的C++头文件。;-) - DevSolar
你在两个示例中都包含了相同的头文件,那么编译时间怎么可能会变长呢? - Jonathan Wakely
但它并没有证明这一点!您是指在sample.hsample.c都最终说#include“my_needed_file.h”的情况下,可能是间接的吗?还是my_needed_file.h包含另一个头文件已经包含的内容?例如,几个头文件都#include <vector>?不太清楚您所询问的内容以及您认为会增加编译时间的原因。 - Jonathan Wakely
如果存在“可能已经在其他地方包含的包括文件,例如string、vector等”,那么它们在两个示例中仍然被包含相同次数……你所做的只是将包含指令从一个文件移动到另一个文件,而不是减少包含指令的数量(除非有一些其他你认为是“显而易见”的差异)。无论如何,在现代编译器中,多次包含头文件不会影响编译时间,因此保持您的头文件干净,并忘记多次包含文件的成本。 - Jonathan Wakely
显示剩余2条评论
6个回答

7

并没有真正的标准最佳实践,但对于大多数账户,您应该在头文件中包含您真正需要的内容,并预先声明您可以的内容。

如果实现文件需要头文件中没有明确要求的内容,则该实现文件应该自行包含它。


1
顺便说一下:您应该在头文件中包含所有需要的内容...我真的很讨厌头文件的包含顺序很重要;) - danielschemmel
1
@dionadar请确保您真正了解此情况中“需要”的含义。您可能认为您“需要”包含一个头文件,但是使用前向声明就足够了。 - Luchian Grigore
当然。我想到了一个非常有趣的例子,就是有人在我们代码库中为一堆类型声明了::std::ostream& operator<<(::std::ostream&, T);,但却从未在包含它的头文件中包含<ostream>。这对他来说并不成问题,因为他总是在其他头文件之前包含iostream - danielschemmel
1
如果您在头文件中没有实际实现<<运算符(或者实现只是转发到不在头文件中的成员函数),那么您应该包含<iosfwd>,而不是完整的<istream>和/或<ostream> - James Kanze
@LuchianGrigore,这通常是我的方法。使包含尽可能独立。 - Devolus

3
该语言没有任何要求,但几乎普遍接受的编码规则是所有头文件都必须是自给自足的;一个只包含单个语句(包括include)的源文件应该可以编译通过。通常验证这一点的方法是在实现文件中最先包含其头文件。
而且编译器只需要读取每个include一次。如果它可以确定已经读取了文件,并且在读取它时检测到了include保护模式,那么它就不需要重新读取文件;它只需检查控制预处理器标记是否已经定义。(有一些配置使得编译器无法确定所包含的文件是否与之前包含的文件相同。在这种情况下,它确实需要重新读取文件并重新解析。然而,这种情况相当罕见。)

2
一个头文件应该被视为一个API。假设你正在为一个客户编写一个库,你将为他们提供一个头文件以包含在他们的代码中,并提供一个已编译的二进制库以进行链接。
在这种情况下,在您的头文件中添加“#include”指令会给您的客户和您带来很多问题,因为现在您必须提供不必要的头文件才能使编译正常。尽可能多地使用前向声明可以使API更加清晰。它还使您的客户可以在需要时实现自己的函数。
如果您确定您的头文件永远不会在当前项目之外使用,则两种方法都不是问题。如果您使用了include guards,那么编译时间也不是问题,而这本来就应该使用。

当然,我只在头文件中包含特定的内容。但是,当我需要字符串时,如果我包含#include <string>,很可能其他头文件也会这样做。但另一方面,如果我不包含它们,那么就必须有人包含它们,从而迫使用户自己查找所需内容,而不是像一致的API一样拥有它。 - Devolus

1
另一方面,如果我将包含文件放在include文件中,编译时间可能会变长,因为即使使用了include guards,也可能会重新打开和重新标记文件。如果您的编译器无法记住哪些文件具有包含保护并避免重新打开和重新标记文件,则需要更好的编译器。大多数现代编译器已经做到了这一点很多年,因此包含相同文件多次(只要它具有包含保护)没有任何成本。例如,请参见http://gcc.gnu.org/onlinedocs/cpp/Once_002dOnly-Headers.html。头文件应该是自给自足的,并包括/声明它们所需的内容。期望用户包含其依赖项的头文件是不好的实践,也是让用户讨厌您的好方法。
如果在sample.h中需要my_needed_file.h(因为sample.h需要其中的声明/定义),那么它应该被包含在sample.h中,毫无疑问。如果它不是sample.h所需的,只需要在sample.c中包含它,而我的首选是在sample.h之后包含它,这样如果sample.h缺少任何需要的头文件,你会更早地知道它:
// sample.c
#include "sample.h"
#include "my_needed_file.h"
#include ...
#include <std_header>
// ...

如果您使用这个#include顺序,那么它会强制使sample.h自给自足,从而确保您不会为头文件的其他用户造成问题和烦恼。

1
拥有更多(不需要的)头部包含意味着在界面层面上显示更多(不需要的)符号。这可能会造成大量混乱,可能导致符号冲突和膨胀的接口。

-1

我认为第二种方法更好,原因如下。

当您在头文件中有一个函数模板时。
class myclass
{
    template<class T>
    void method(T& a)
    {
       ...
    }
}

你不想在myclass.cxx的源文件中使用它。但是你想在xyz.cxx中使用它,如果你遵循第一种方法,那么你将最终包含所有myclass.cxx所需的文件,这对于xyz.cxx没有任何用处。

这就是我现在对于差异的全部想法。因此,我建议采用第二种方法,因为它可以使你的代码在将来更容易维护。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接