前向声明或自给自足的标题?

时间:2012-01-20 20:03:31

标签: c++ coding-style header include

Google的C ++风格指南says,“当前向声明就足够时,不要使用#include。”

但是C++ Coding Standards(Sutter和Alexandrescu),第23项是“让头文件自给自足。负责任地行事:确保你编写的每个头文件都是独立编译的,通过让它包含其内容所依赖的任何头文件在“

哪种策略最好?

7 个答案:

答案 0 :(得分:4)

依赖关系管理在C ++中非常重要:如果更改头文件,则需要编译依赖于此头文件的所有转换单元。这可能非常昂贵。因此,您希望头文件最小化,因为它们不包含任何不需要包含的内容。这就是谷歌的建议。

如果您需要某个组件,则应包含该组件的标头。但是,除了获取声明的组件之外,您不应该要求包含任何内容。也就是说,每个头文件都必须编译而不包含任何其他内容。这是赫伯和安德烈给出的建议。请注意, only 适用于获取声明:如果您要使用这些声明中的任何一个并且这需要另一个组件,您可能还需要包含此组件的头文件。

然而,这两个建议是一致的!它们都非常有价值,应该毫不妥协地遵循它们。这基本上意味着,如果您只需要声明的类,那么您更愿意声明一个包含其标题的类。也就是说,如果类只出现声明,在指针或引用定义,参数列表或返回类型中,则只需要声明。如果您需要了解有关该课程的更多信息,例如因为它是一个基础或被定义的类的成员或者使用一个对象,例如在内联函数中,您需要定义。实际上,如果您需要了解任何成员或类的大小,您需要该类的定义。

一个有趣的转折是类模板:只有第一个类模板声明才能定义默认参数!但是,类模板可以多次声明。要在声明类模板时使头文件最小,您可能希望为所涉及的类模板设置特殊的转发头,这些头模板仅声明类模板及其默认参数。然而,这是实施土地的方式......

答案 1 :(得分:3)

Sutter和Alexandrescu关于项目#22说“不要过度依赖:当前瞻性宣言要做时,不要#include定义。”

就个人而言,我同意这一说法。如果在我的A类中,我没有使用B类的任何功能,也没有实例化B类的对象,那么我的代码不需要知道如何制作B类。我只需要知道它存在。

前向声明对于打破循环依赖关系也很有用......

编辑:我也非常喜欢Mark B指出的观察结果:有时你不会包含文件a.hpp,因为它已经包含在你所包含的文件b.hpp中,它选择包括a.hpp即使前向声明已经足够了。如果停止使用b.hpp中定义的函数,则代码将不再编译。如果b.hpp的程序员使用了前向声明,那就不会发生这种情况,因为你会在代码中的其他地方包含a.hpp ......

答案 2 :(得分:2)

我说这两个陈述都是正确的。如果您的头文件只包含指针或对某些数据类型的引用,那么您只需要一个前向声明。

但是,如果您的标头包含特定类型的对象,则应包括定义该类型的标头。来自Sutter和Alexandrescu的建议告诉您不要依赖标题的使用者通过包含所需的类型定义来解决此类引用。

您可能还想查看Pimpl IdiomCompilation firewalls(或C++11 version)。

答案 3 :(得分:2)

我相信他们都说的完全一样。

假设您有一个方法,通过引用Bar引用您的头文件,但该方法在源文件中定义。标题中的前向声明显然足以使标题独立编译。

现在让我们看一下用户代码。如果客户端只是传递从其他地方转发的引用,那么它们根本不需要Bar的定义,一切都很好。如果他们以某种其他方式使用Bar,则该源文件强制要求使用Bar NOT 标题。事实上,如果您在标头中包含不需要的Bar包含,那么如果客户端停止需要您的包含并将其删除,那么他们的其他Bar代码会突然停止工作,因为它们从未正确包含它在他们自己的源文件中。

现在假设您的标头使用std::string。在这种情况下,为了使标题独立编译,您必须包含<string>,这两个指南都会告诉您这样做(谷歌因为前向声明不会这样做,而Sutter和Alexandrescu允许标题编译独立)。

答案 4 :(得分:1)

Sutter和Alexandrescu在这里可能更正确。如果我转发声明class Foo;由于某种原因存在于bar.h并且不包含bar.h,请运气好,试图找到Foo的声明(特别是如果代码库很大)。

答案 5 :(得分:1)

除了其他答案所涵盖的内容之外,还有一些情况是必须使用前向声明,因为相互依赖。

FWIW,如果我只需要一个类型名称,我通常会向前声明它。如果它是一个函数声明,我通常会包含它(尽管这些情况很少见,因为非成员函数很少见,并且必须包含成员函数)。

如果它是extern "C"函数,我从不转发声明是因为链接器无法告诉我是否搞砸了参数类型。

答案 6 :(得分:1)

这两个建议完全相互兼容。您可以同时关注它们。它们绝不是相互排斥的 - 它不是“任何一种”情况。

标题可以使用前向声明,只需要所有内容,并且仍可单独编译:

// Foo.hpp
class Bar; // Forward declaration

class Foo {
public:
    void doSomethingFancy ();

private:
    Bar *bar;
};

在上面的示例中,Foo.hpp是自给自足的,即使使用前向声明声明Bar类而不是包含Bar.hpp标题。