#include 标题样式
Posted
技术标签:
【中文标题】#include 标题样式【英文标题】:#include header style 【发布时间】:2009-10-28 00:10:22 【问题描述】:我对包含标题时的“最佳实践”有疑问。
显然包含保护可以防止我们在头文件或源文件中包含多个包含,所以我的问题是您是否发现在头文件或源文件中#include 所有需要的头文件是否有益,即使其中包含一个头文件已经包含其他包含之一。这样做的原因是为了让读者可以看到文件所需的所有内容,而不是在其他标题中寻找。
例如:假设使用了包含防护:
// Header titled foo.h
#include "blah.h"
//....
.
// Header titled bar.h that needs blah.h and foo.h
#include "foo.h"
#include "blah.h" // Unnecessary, but tells reader that bar needs blah
另外,如果头文件中不需要头文件,但在其相关的源文件中需要,你是把它放在头文件中还是放在源代码中?
【问题讨论】:
它告诉读者 foo 现在需要 blah。但是,如果 foo 被更改为使用 fubar,那么您现在是否会在所有来源中搜索 foo.h 和 blah.h,以便您可以将 blah.h 替换为 fubar.h 【参考方案1】:在您的示例中,是的,bar.h 应该#include blah.h。这样,如果有人修改 foo 使其不需要 blah,则更改不会破坏 bar。
如果在 foo.c 中需要 blah.h 而在 foo.h 中不需要,那么它不应该在 foo.h 中#included。许多其他文件可能#include foo.h,更多文件可能#include them。如果您在 foo.h 中 #include blah.h,那么您会使所有这些文件不必要地依赖 blah.h。不必要的依赖会引起很多麻烦:
如果修改 blah.h,则必须重新编译所有这些文件。 如果您想隔离其中一个(例如,将其转移到另一个项目或围绕它构建单元测试),您必须使用 blah.h。 如果其中一个存在错误,则在您检查之前,您不能排除 blah.h 是原因。 如果您愚蠢到在 blah.h 中包含宏之类的东西……好吧,没关系,在这种情况下,您就没有希望了。【讨论】:
关于编译依赖,我喜欢 Sutter 的谜题:gotw.ca/gotw/007.htm【参考方案2】:基本规则是,#include
您在代码中实际使用的任何标头。所以,如果我们在谈论:
// foo.h
#include "utilities.h"
using util::foobar;
void func()
foobar();
// bar.h
#include "foo.h"
#include "utilities.h"
using util::florg;
int main()
florg();
func();
如果bar.h
使用了两次包含的标头中的工具,那么您应该#include
它,即使您不一定必须这样做。另一方面,如果bar.h
不需要utilities.h
中的任何函数,那么即使foo.h
包含它,也不要#include
它。
【讨论】:
【参考方案3】:源文件的标头应定义代码用户准确使用它所需的接口。它应该包含他们使用界面所需的所有内容,但没有额外内容。如果他们需要 xyz.cpp 提供的工具,那么用户只需要#include "xyz.h"
。
“xyz.h”如何提供该功能很大程度上取决于“xyz.h”的实现者。如果它需要只能通过包含特定标头来指定的设施,则“xyz.h”应包含该其他标头。如果它可以避免包含特定的标头(通过前向定义或任何其他干净的方式),它应该这样做。
在示例中,我的编码可能取决于“foo.h”标头是否与“blah.h”标头受同一项目的控制。如果是这样,那么我可能不会做出明确的第二个包含;如果没有,我可能会包括它。但是,上面的陈述应该迫使我说“是的,包括 'foo.h' 以防万一”。
在我的辩护中,我相信 C++ 标准允许包含任何一个 C++ 头文件以包含其他头文件 - 根据实现的要求;这可以看作是相似的。问题是,如果您只包含 'bar.h' 并使用来自 'blah.h' 的功能,那么当 'bar.h' 被修改时,因为它的代码不再需要 'blah.h',那么用户的代码以前编译(偶然)现在失败了。
但是,如果用户直接访问“blah.h”设施,那么用户应该直接包含“blah.h”。 'bar.h' 中代码的修改后的接口不再需要'blah.h',所以任何只使用'bar.h' 接口的代码都应该没问题。但是如果代码也使用了'blah.h',那么它应该直接包含它。
我怀疑得墨忒耳法则也应该被考虑——或者可以被视为对此产生影响。基本上,“bar.h”应该包含使其工作所需的标头,无论是直接还是间接 - 并且“bar.h”的使用者不需要太担心。
回答最后一个问题:很明显,实现需要但接口不需要的头文件应该只包含在实现源代码中,绝对不能包含在头文件中。实现使用什么与用户无关,编译效率和信息隐藏都要求头文件只向头文件的用户公开最少的必要信息。
【讨论】:
【参考方案4】:在 C++ 的标头中预先包含所有内容可能会导致编译时间爆炸
最好尽可能封装和转发声明。前向声明为使用该类所需的内容提供了足够的提示。不过,在其中包含标准包含是完全可以接受的(尤其是模板,因为它们不能被前向声明)。
【讨论】:
提问者想知道是否将#include
d 文件的依赖项包含为#includes
,而不是如何使用包含。
同样正确:“C++ 会导致编译时间爆炸”
我引用:“另外,如果头文件中不需要一个头文件,但在它的相关源文件中需要它,你是把它放在头文件中还是放在源文件中?”【参考方案5】:
我的 cmets 可能无法直接回答您的问题,但很有用。
IOD/IOP 鼓励在 INTERFACE 标头中尽可能少地放置标头,这样做的主要好处是:
-
更少的依赖;
更小的链接时间符号范围;
编译速度更快;
如果标头包含静态 C 样式函数定义等,则最终可执行文件的大小会更小。
对于每个 IOD/IOP,接口应该只放在 .h/.hxx 头文件中。在您的 .c/.cpp 中包含标题。
【讨论】:
【参考方案6】:我的头文件规则是:
规则 #1
在头文件中,仅包含类的成员或基类的#include 类。 如果您的类有指针或引用,则使用前向声明。
--Plop.h
#include "Base.h"
#include "Stop.h"
#include <string>
class Plat;
class Clone;
class Plop: public Base
int x;
Stop stop;
Plat& plat;
Clone* clone;
std::string name;
;
Caviat:如果您在头文件(示例模板)中定义成员,那么您可能需要包含 Plat 和 Clone(但仅在绝对需要时才这样做)。
规则 #2
在源代码中按从最具体到最不具体的顺序放置头文件。 但不要包含您没有明确需要的任何内容。
所以在这种情况下,您将包括:
Plop.h(最具体的)。 Clone.h/Plat.h(类中直接使用) C++ 头文件(Clone 和 Plat 可能依赖于这些) C 头文件这里的论点是,如果 Clone.h 需要映射(而 Plop 需要映射)并且您将 C++ 头文件放在更靠近列表顶部的位置,那么您隐藏了 Clone.h 需要映射的事实,因此您可能不会添加它在 Clone.h 中。
规则 #3
始终使用标题保护
#ifndef <NAMESPACE1>_<NAMESPACE2>_<CLASSNAME>_H
#define <NAMESPACE1>_<NAMESPACE2>_<CLASSNAME>_H
// Stuff
#endif
PS:我不建议使用多个嵌套命名空间。我只是在展示如果我这样做的话我会怎么做。我通常把所有东西(除了主要的)都放在一个命名空间中。嵌套将取决于情况。
规则 #4
避免使用 using 声明。 除了我正在学习的课程的当前范围:
-- Stop.h
#ifndef THORSANVIL_XXXXX_STOP_H
#define THORSANVIL_XXXXX_STOP_H
namespace ThorsAnvil
namespace XXXXX
class Stop
;
// end namespace XXXX
// end namespace ThorsAnvil
#endif
-- Stop.cpp
#include "Stop.h"
using namespace ThorsAnvil:XXXXX;
【讨论】:
可以说,我使用的正是规则 #2 的倒数。原因是我不希望“更具体”的标题意外污染包含“不太具体”的标题的上下文。 我不同意你的理由,但关于这个主题的争论仍在进行中(可能永远不会得到解决),我承认这是另一种合适的方法。我的主要规则 #0 就是保持一致。以上是关于#include 标题样式的主要内容,如果未能解决你的问题,请参考以下文章