【问题标题】:Placing methods in .h vs. .cpp files将方法放在 .h 与 .cpp 文件中
【发布时间】:2015-09-01 11:42:30
【问题描述】:

我已经看到将一个类的代码放在单独的 C++ 中,而方法定义放在头文件中。我的第一次 OOP 经验是使用 Java,其中所有方法都放在类文件中,我实际上更喜欢这个。

将我所有的方法放在头文件中是否会影响编译器生成的汇编代码?

如果是这样,将一个类的整个代码放在它的头文件中是否会损害性能?

【问题讨论】:

    标签: c++ optimization compiler-construction header


    【解决方案1】:

    关键是复杂的 C++ 程序是通过编译多个对象,然后将它们链接在一起来创建的。每个对象通常来自编译一个实现文件(例如“.cpp”、“.cc”等),这些文件可能直接或间接包含许多头文件。因此,如果您编写了一个好的类并将代码放入头文件中,那么该代码可以包含在多个目标文件中,然后编译器会冗余地生成它,而且 - 链接器不会(也不容易)比较版本以查看它们是否等效并删除冗余副本(如果使用相对地址更容易 - “位置无关代码” - 但这是另一回事)。另请参阅下面 jalf 的评论。

    因此,您不希望标题中出现不同的外联函数。如果它们名义上是 inline 函数 - 由于使用 inline 关键字或在类中定义 - 那么编译器将只需要承担额外的工作并确保它们的任何外联版本都是唯一表示的在可执行文件中。但是,对于不符合要求的函数,程序员的负担仍然存在。

    此外,如果您在标头中提供实现,则会为每个对象进行冗余编译,并且对标头的任何更改都将强制重新编译所有依赖对象。可以更改单独对象中的外联函数,重新编译该单个对象,然后可以将其与其他预先存在的对象链接以形成新的可执行文件。在大型项目中,这可以节省大量编译时间。

    【讨论】:

    • +1 我真的很喜欢你的回答。但是出于好奇,您对模板有什么看法?我见过很多模板代码要么包含额外的“内联”标题,要么是一大堆方法。
    • @GWW:为每种类型组合实例化模板,这使得它们非常快速和强大,但高度依赖于客户端代码。纵观 C++ 历史,没有人找到避免需要通过标头公开“实现”的好方法。 #include-ing 另一个文件有时可能有助于保持 API 分离,就像将函数定义放在类/结构声明之后一样,但最佳结果因许多因素而异(函数长度、文档的数量和样式、复杂性等、数量/技能客户端用户与实现维护者的比例)。它凌乱的:-)
    • 链接器不需要检查一个类定义的两个实例是否相同(我不知道有一个链接器这样做)。如果它们具有相同的名称,它通常假定它们是相同的,并删除一个或另一个。不违反 ODR 的责任始终在程序员身上。
    • 这就是我从阅读中得出的结论。太糟糕了,他们必须如此凌乱。
    【解决方案2】:

    标题/实现拆分有几个正当理由 单独编译:
    1。这可能是一项工作要求 - 例如,您正在提供一个 二进制库+标题给某人,或者你的同事太保守了 接受其他任何事情。
    2。它仍然需要开发非常大的项目(例如,> 10M 的源代码), 因为在每次修改后重建整个应用程序会变得很痛苦。 (但将 jpeglib 或 zlib 之类的东西编译为单个模块应该仍然可以)
    3。有一种观点认为使用头文件作为参考更容易,看 up函数等。 (但通常最好编写适当的文档;与标题不同,文档中的错误不太可能影响您的程序)

    此外,还有更多理由不再使用它:
    1。您希望避免维护重复的代码。
    2。类方法不需要前向声明
    3。无论如何,模板只能在标题中声明
    4。不需要函数内联的情况实际上相当罕见, 即在一个紧密的循环中多次调用大函数,但是有 noinline 属性和 PGO。否则内联会提高速度。 至于代码膨胀,无论如何,大多数库已经很大了。
    5。总体而言,作为单一来源编译的程序更快更小, 因为编译器可以做得更好。
    6。如果没有标头,源通常会小两倍左右, 并且编译器将能够正确检查语法,因此您将无法 意外地将 extern "C" cdecl 函数原型链接到变量作为实现。 总的来说,它会更便携,因为不同的链接器对名称匹配有不同的想法。
    7。它很奇怪,但经常使用动态分配只是因为 标题样式 - 可以通过定义所有 单个类中的详细信息,但人们更喜欢使用指向部分类声明的指针(然后寻找内存泄漏)。

    现在,单独的对象模块有几个奖励点:
    4。 gcc 中的 PGO 统计信息是按对象模块生成的,这似乎是使用单个可执行文件“基准测试”几种不同操作模式的唯一方法。
    5。可以使用不同的编译器选项编译不同的模块以优化速度。也有一些编译器扩展,但它们不是很可靠。
    6。有时,当您修改某些内容时,编译器可能会对代码的另一部分做一些奇怪的事情 - 但通常它不能传播到对象模块之外。

    【讨论】:

      【解决方案3】:

      是的,放在 header 中的方法是内联的,所以它们通常更快(尤其是短的)。

      主要的缺点是头文件中的每次修改都会导致包含它的每个文件重新编译。

      【讨论】:

      • “放在头文件中的方法是内联的”——在有经验的 C++ 程序员的代码中,这是真的,但初学者(例如 seljuq70)经常犯错误,将非内联函数放在头文件中。从技术上讲,只有在 class/struct 中定义的(即使用它们的主体)或指定 inline 关键字的函数是内联的。它们应该是标题中的唯一函数,但可能不是。
      • @Tony:这就是为什么我投票给你更详细的答案。但考虑到他们通常知道最好有许多短函数,这就足够了。
      • 好吧,我喜欢你的观点......“标题中的简单函数”是初学者使用的简单指南,并且不会太错误 - 无论如何,你的重新编译解释都涵盖了这一点...... + 1.
      猜你喜欢
      • 2015-09-15
      • 2023-03-10
      • 1970-01-01
      • 1970-01-01
      • 2021-12-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多