【问题标题】:Is anyone familiar with the "implementation/internal header" (*.ih)?有人熟悉“实现/内部标头”(*.ih)吗?
【发布时间】:2012-06-19 06:31:58
【问题描述】:

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

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

这两种选择的缺点都很明显:
1a) 在添加任何需要额外 #include's 的内容后,您必须重新编译所有源代码 #include'ing 这个头文件。
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";
}

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

【问题讨论】:

  • 废话,因为您必须为每个班级管理一个额外的文件,这一切都不会改变,因为#include 只是简单的复制粘贴。至少如果我在您的问题中没有遗漏任何内容。
  • 不,它不会改变任何东西,但会增加代码的可读性。这将永远是一个个人问题,但我更喜欢其中的 6 个小#include "blabla.ih",特别是如果这 6 个在您的很多资源中重复。
  • 废话,每个实现文件都会有不同的所需标题 - 很可能。而且,当您在没有某种奇怪的另一层间接性的情况下包含它们时,它会扼杀而不是提高它的可读性,因为阅读该文件的人必须查看另一个文件以找出它真正包含的内容。跨度>
  • 好吧,这显然不适合你!幸运的是,我并没有试图说服任何人或告诉任何人这样做;-) 只是对这个公约的其他观点感到好奇。

标签: c++ header include implementation internal


【解决方案1】:

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

首先,海报写道:

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

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

接下来,海报说:

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

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

发帖人提出的下一点:

1b) 你的头文件,应该是你的类的清晰接口,得到 被一大堆#include指令污染,用户不知道是什么 它们是用来做的,他也不在乎。

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

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

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

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

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

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

【讨论】:

    【解决方案2】:

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

    【讨论】:

    • 你是对的,如果你选择以这种方式组织你的代码。当然,我是在“一个功能,一个文件”规则(或者我应该说“法律”)占主导地位的教室里长大的。不管对某些人来说这似乎毫无意义,它可以帮助我组织代码。我不必滚动资源来查找函数,只需单击 functionname.cc 就可以了。花哨的编辑器摆脱了这个问题,但我没有使用。不过,这只是个人喜好问题,但我喜欢让源文件完全适合我的屏幕。
    • @user1428839:这是“其他人以完全不同的方式做事”的问题。
    • 嗯,所以我是弃儿?难怪我最近感到如此孤独……再说一次,其他人都喜欢流行音乐……那可不是好音乐! ;-)
    【解决方案3】:

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

    【讨论】:

    • 在这种情况下,包含(或前向声明,如果足够的话)当然必须在类头中。而且我可以从经验中看出,增益/工作比率确实是有益的,因为创建一个额外的文件并没有那么多工作,而重新编译可能需要一段时间(10 分钟对我来说是一段时间......)
    • @user1428839,您仍然需要重新编译所有要编译的内容,因为#include 只是普通的复制粘贴,正如我在 OP 下的评论中标记的那样 - 无论您如何包含所有内容,对于编译器来说,它总是以相同的形式结束。
    • @Griwes 这并不总是正确的。如果要更改其中一个实现,需要一个不同的 #include 指令,该指令通常位于 .h 文件中,则必须重新编译包括此 .h 文件在内的所有源。从所有来源来看,我的意思是 THIS 类的所有实现,以及包括它在内的其他类的所有实现。
    • @user1428839,我认为您的问题是您 1) 不了解 #include 的工作原理和 2) 不了解前向声明。阅读它们,您会找到答案 - 并了解整个 .ih 的事情都是无稽之谈。
    • @user1428839 从cmets对这个问题来看,Griwes就是喜欢说“废话”!
    猜你喜欢
    • 2011-03-04
    • 2011-05-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-08
    相关资源
    最近更新 更多