3

在 FB Brokken(The C++ Annotations 的作者)的半年 C++ 课程中,我们学会了使用所谓的实现头文件。我知道这是弗兰克的约定,但实际上我从未在其他任何地方见过它。因此,我将解释这个概念,我很好奇其他人对此有何看法。

这个想法是,您只需将#include类成员的实现所需的所有指令(假设您没有编写类内定义)放在一个文件中,.ih 实现标头和#include每个源中的该文件文件。替代方案是
1)#include类头文件中的所有内容或
2)#include每个源文件中的所有头文件。

这两种替代方案的缺点是显而易见的:
1a)#include在添加任何需要额外#include的 's 后,您必须重新编译所有源文件。
1b)你的头文件,应该是你的类的清晰接口,被一大堆#include指令污染,用户不知道它们的用途,他也不关心。2a)您必须#include在每个源文件中一次又一次地使用相同的标题。
2b)你的实现被所有这些污染了#include,让它看起来有点乱。

只是要清楚:

/* someclass.h(pp) */

#ifndef SOME_CLASS_H
#define SOME_CLASS_H

class SomeClass
{
    //some private data members
    public:
        SomeClass();
        void sayHi() const;
        // some more member functions
    private:
        // some private member functions
};

#endif

/* someclass.ih */

#include "someclass.h"
#include <iostream>
#include <vector>
using namespace std; 

// namespace is now only used in my implementations, other people
// including my headers won't accidentally import the entire std namespace.

/* sayhi.cc */

#include "someclass.ih"

void SomeClass::sayHi() const
{
     cout << "sayHi() says hi!\n";
}

现在,再次,问题是:有没有人听说过这样的约定?我说服任何人开始使用它了吗?我个人觉得它是一个非常有用(甚至是显而易见的)约定,我有点惊讶我在其他任何地方都没有看到它。

4

3 回答 3

7

有趣的帖子。让我首先评论 user1428839(以下称为海报)关于 .ih 标头的声明:

首先,海报写道:

...这个想法是,您只需将类成员的实现所需的所有#include 指令...放在一个文件中,.ih 实现头文件中,并将 #include 这个文件放在每个源文件中.

按照公式,这是不正确的。为避免混淆,句子的最后一部分应该是: ... 在.ih 文件所属的类的每个源文件中。在 .ih 方法下,类文件(例如,someclass.h)仅提供接口并且应该只声明可以声明的内容。所以,如果SomeClass有一个类数据成员std::ofstream *d_out,那么就不需要#include <fstream>. 相反,它就足够了#include <iosfwd>。这会产生一个干净的类接口,当包含不属于但仅使用SomeClass.

接下来,海报说:

1a) 在添加任何需要额外#include 的内容后,您必须重新编译所有源#include'ing 这个头文件。

这不是真的。仅当附加标头声明的功能在接口中实际使用时才需要这样做如果不是这样,则不需要完全重新编译。如果添加了新的数据成员,或者(但这是不好的风格,例如,您正在使用内联成员)在类接口中使用了附加标头的元素,需要完全重新编译。

海报提出的下一点:

1b)你的头文件,应该是你的类的清晰接口,被一大堆#include指令污染,用户不知道它们的用途,他也不关心。

正确,但这里的重点是类头变得太胖了。每次外部源需要包含someclass.h时,编译器还必须读取所有那些额外的头文件,以及那些头文件所包含的头文件等,而它只需要知道它们的本质SomeClass是什么。格言是用户不应该为他/她不需要的东西付费,在这种情况下,由于编译器必须读取(通常很多)无用的(在上下文中SomeClass)头文件而导致额外的编译时间。

使用海报建议的 .ih 约定的替代方法之一是将您需要的内容包含在您需要的地方。事实上,我认为这是一个很好的选择,但它也需要大量的簿记和工作。通常类成员实现需要相同的头文件,并将它们放在一个文件中,实现头具有定义一个维护点的额外好处。是的,有时编译器必须读取特定源不需要的标头时会有一点开销,但这只会发生一次:在类编译时,这(希望,大概)只是偶尔发生。

恐怕对海报发布的一些反应是对 .ih 方法的核心观点略有误解的结果:

  • 类头本身保持尽可能苗条
  • 使用该类的源不会遭受比严格要求更长的编译时间
  • 类开发人员不必一次又一次地包含编译类成员所需的一堆头文件。相反,只需要包含一个根据类的要求定制的标题。

另一个回复(来自 DeadMG)侧重于将所有源代码放入一个源文件中。在某种程度上,这是一个风格问题。如果您为某个特定程序开发软件,那么您不妨将所有源代码放入一个文件中,前提是这符合您的个人喜好。我个人觉得使用这些文件非常烦人且很难,因为我经常喜欢并行查看或使用许多函数的源代码,但最终这当然是个人喜好问题。

但是,如果您开发软件的目的是稍后在其他程序中重用它,例如,您开发一个考虑将其添加到库中的类,那么您绝对应该使用单功能单文件方案。考虑构造函数:类通常提供一系列构造函数,您可以选择适合您的上下文的构造函数。如果将类的实现放入一个源文件中,那么您的最终程序将过于臃肿,因为链接器必须将实现您的构造函数的目标文件以及该程序所需的目标文件添加到您的程序中,等等,等等。所以你最终得到了一个程序,它完全没有意义地链接到许多额外的目标文件。一功能一文件原则防止这种情况发生。

于 2012-06-17T10:14:53.560 回答
3

我从来没有见过这个在任何地方使用过。大多数类都将所有实现的功能放在一个文件中,因此重复指定必要的标头没有问题。

于 2012-06-16T13:15:06.550 回答
1

我不明白。当您拥有使用来自其他头文件的类或 struts 或 typedef 的类成员或类方法参数时会发生什么?而且,再加上重新编译的问题……这似乎是为了获得更多的工作。您应该仅在标头中包含标头所需的内容,并且仅在 impl 中包含实现所需的内容。而已。

于 2012-06-16T12:26:21.623 回答