【发布时间】:2015-09-01 11:42:30
【问题描述】:
我已经看到将一个类的代码放在单独的 C++ 中,而方法定义放在头文件中。我的第一次 OOP 经验是使用 Java,其中所有方法都放在类文件中,我实际上更喜欢这个。
将我所有的方法放在头文件中是否会影响编译器生成的汇编代码?
如果是这样,将一个类的整个代码放在它的头文件中是否会损害性能?
【问题讨论】:
标签: c++ optimization compiler-construction header
我已经看到将一个类的代码放在单独的 C++ 中,而方法定义放在头文件中。我的第一次 OOP 经验是使用 Java,其中所有方法都放在类文件中,我实际上更喜欢这个。
将我所有的方法放在头文件中是否会影响编译器生成的汇编代码?
如果是这样,将一个类的整个代码放在它的头文件中是否会损害性能?
【问题讨论】:
标签: c++ optimization compiler-construction header
关键是复杂的 C++ 程序是通过编译多个对象,然后将它们链接在一起来创建的。每个对象通常来自编译一个实现文件(例如“.cpp”、“.cc”等),这些文件可能直接或间接包含许多头文件。因此,如果您编写了一个好的类并将代码放入头文件中,那么该代码可以包含在多个目标文件中,然后编译器会冗余地生成它,而且 - 链接器不会(也不容易)比较版本以查看它们是否等效并删除冗余副本(如果使用相对地址更容易 - “位置无关代码” - 但这是另一回事)。另请参阅下面 jalf 的评论。
因此,您不希望标题中出现不同的外联函数。如果它们名义上是 inline 函数 - 由于使用 inline 关键字或在类中定义 - 那么编译器将只需要承担额外的工作并确保它们的任何外联版本都是唯一表示的在可执行文件中。但是,对于不符合要求的函数,程序员的负担仍然存在。
此外,如果您在标头中提供实现,则会为每个对象进行冗余编译,并且对标头的任何更改都将强制重新编译所有依赖对象。可以更改单独对象中的外联函数,重新编译该单个对象,然后可以将其与其他预先存在的对象链接以形成新的可执行文件。在大型项目中,这可以节省大量编译时间。
【讨论】:
标题/实现拆分有几个正当理由
单独编译:
1。这可能是一项工作要求 - 例如,您正在提供一个
二进制库+标题给某人,或者你的同事太保守了
接受其他任何事情。
2。它仍然需要开发非常大的项目(例如,> 10M 的源代码),
因为在每次修改后重建整个应用程序会变得很痛苦。
(但将 jpeglib 或 zlib 之类的东西编译为单个模块应该仍然可以)
3。有一种观点认为使用头文件作为参考更容易,看
up函数等。 (但通常最好编写适当的文档;与标题不同,文档中的错误不太可能影响您的程序)
此外,还有更多理由不再使用它:
1。您希望避免维护重复的代码。
2。类方法不需要前向声明
3。无论如何,模板只能在标题中声明
4。不需要函数内联的情况实际上相当罕见,
即在一个紧密的循环中多次调用大函数,但是有
noinline 属性和 PGO。否则内联会提高速度。
至于代码膨胀,无论如何,大多数库已经很大了。
5。总体而言,作为单一来源编译的程序更快更小,
因为编译器可以做得更好。
6。如果没有标头,源通常会小两倍左右,
并且编译器将能够正确检查语法,因此您将无法
意外地将 extern "C" cdecl 函数原型链接到变量作为实现。
总的来说,它会更便携,因为不同的链接器对名称匹配有不同的想法。
7。它很奇怪,但经常使用动态分配只是因为
标题样式 - 可以通过定义所有
单个类中的详细信息,但人们更喜欢使用指向部分类声明的指针(然后寻找内存泄漏)。
现在,单独的对象模块有几个奖励点:
4。 gcc 中的 PGO 统计信息是按对象模块生成的,这似乎是使用单个可执行文件“基准测试”几种不同操作模式的唯一方法。
5。可以使用不同的编译器选项编译不同的模块以优化速度。也有一些编译器扩展,但它们不是很可靠。
6。有时,当您修改某些内容时,编译器可能会对代码的另一部分做一些奇怪的事情 - 但通常它不能传播到对象模块之外。
【讨论】:
是的,放在 header 中的方法是内联的,所以它们通常更快(尤其是短的)。
主要的缺点是头文件中的每次修改都会导致包含它的每个文件重新编译。
【讨论】:
class/struct 中定义的(即使用它们的主体)或指定 inline 关键字的函数是内联的。它们应该是标题中的唯一函数,但可能不是。