仅标头库更有效吗?


48

假设条件

  1. C ++的仅标头库的优点之一是它们不需要单独编译。

  2. inline仅当函数在头文件*中定义时,在C和C ++中才有意义。

  3. 传统上,在C中,使用.c / .h布局,其中标头代表翻译单元的最小公共接口。同样,.cpp / hpp。

仅标头库通常比传统布局在代码和执行时间上更有效吗?如果是这样,是否是因为进行了广泛的内联或其他优化?

*-在标头中定义函数使编译器可以在编译任何翻译单元时查看实现,并且实际上使内联代码成为可能


2
您会惊讶于许多现代C ++链接器(GCC,MSVC,ICC等)在不同的翻译单元之间内联代码的效果如何。考虑到优化链接器违背我的期望并设法内联的次数,我会在效率方面说“通常不”(这是不包括在头文件中进行内联或在单独的静态链接库中提供实现的dylib上下文) 。但是,仅标头的库,只要它们在接口和实现方面都稳定,就可以变得很性感,因为它们可以很容易地部署在新项目中。

1
我不知道这应该得到完整的答案,但是仅标头库的一个巨大好处就是易于安装和使用:下载,#include“ lib.h(pp)”,完成。
WeRelic '16

Answers:


44

C ++的仅标头库的优点之一是它们不需要单独编译

不,这不是优势,而恰恰相反-库的主要部分必须在包含时就被编译,而不仅仅是一次。这通常会增加编译时间。但是,如果您指的是Wikipedia此处列出的优点:该文章是有关减少整个构建,打包和部署过程的管理开销的。

在C和C ++中,仅当函数在头文件中定义时,内联才有意义*

这取决于编译器/链接器系统,但是我想对于大多数现有的C和C ++编译器来说都是如此。

传统上,在C中,使用.c / .h布局,其中标头代表翻译单元的最小公共接口。同样,.cpp / hpp。

那基本上是正确的。C ++类头通常不仅仅包含最小的公共接口-它们通常还包含许多私有内容。为了减轻这种情况,使用了PIMPL习惯用法。这类似于仅标头库的“相反”,它试图使必要的标头内容最小化。

但是要回答您的主要问题:这是一个权衡。放入头文件中的库代码越多,编译器就越有机会优化代码以提高速度(如果确实发生这种情况,或者如果增加明显,则是完全不同的问题)。另一方面,头文件中的代码过多会增加编译时间。尤其是在大型C ++项目中,这可能会成为一个严重的问题,请参阅John Lakos撰写的“大型C ++软件设计” -尽管本书有些过时,并且其中描述的某些问题已由现代编译器解决,但总体思路/解决方案仍然有效。

特别是,当您不使用稳定的(第三方)库,而您在项目期间正在开发自己的库时,编译时间就变得很明显。每次在lib中进行更改时,都必须更改头文件,这将导致所有相关单元的重新编译和链接。

恕我直言,仅标头库的流行是由模板元编程的流行引起的。对于大多数编译器,模板库必须是仅标头的,因为编译器仅在提供类型参数时才能启动主编译过程,并且对于完整编译和优化,编译器必须同时“看到两者”-库代码和模板参数值。这样就不可能(或至少很难)为这种库生成任何“预编译”的编译单元。


6
因此,简而言之,仅标头的库更方便而不是更有效;由于C ++没有任何标准的软件包管理器,因此有助于推动采用。
Matthieu M.

6
@MatthieuM:不,编译后的代码有时确实会更有效,对于模板库,仅标头设计通常不是方便的问题。而且增加编译时间绝对不是更方便。
布朗

在标头中包含热代码可能确实会导致更有效的编译代码,但是,这不需要在标头中包含所有代码,因此,IMHO与仅标头库是独立的。
Matthieu M.

1
@MatthieuM .:当然,这是正确的,但是我认为“更方便”一词并不能很好地描述这种情况。
布朗

3
我为以前的雇主在一个精简的旧OS之上进行了一个大型嵌入式项目,在这种情况下,我可以证明编译时间长的痛苦。任何人将太多内容塞入头文件中都会被无情地对待。
Fred Thomsen

15

好吧,让我们首先拆除一些假设:

  1. C ++的仅标头库的优点之一是它们不需要单独编译。

分开编译意味着如果仅更改一部分,则可能不必重新编译所有内容。
因此,不利而不是优势。

  1. 在C和C ++中,仅当函数在头文件*中定义时,内联才有意义。

是的,唯一的影响inline就是one-definition-rule的例外。
但是,如果这些定义在任何方面都不同,那么请告您。

因此,如果函数在编译单元内部,请对其进行标记static。这也使得内联的可能性更大,因为该功能需要可用才能内联。
尽管如此,还是要看看至少在MSVC ++,gcc和clang支持下的链接时间优化。

  1. 传统上,在C中,使用.c / .h布局,其中标头代表翻译单元的最小公共接口。同样,.cpp / hpp。

好吧,仅提供最小的接口无疑是实现更高的API和ABI稳定性并最小化编译时间的目标之一。

尤其是C ++类并没有真正适应这一点,因为所有私有位都泄漏到标头中,无论您是否要从其派生,受保护的位都泄漏到标头中。

设计模式PIMPL用于减少此类细节。

但是,在C ++中将接口和实现完全分开的部分是模板。
该委员会试图对导出的模板做一些事情,但是由于过于复杂而无法正常工作,该模板已被放弃。

现在,他们正在开发适当的模块系统,尽管进展缓慢。这将大大减少编译时间,并且还应通过减小其表面来提高API和ABI的稳定性。

仅标头库通常比传统布局在代码和执行时间上更有效吗?如果是这样,是否是因为进行了广泛的内联或其他优化?

仅标头的库可以在代码大小和执行时间上更有效,尽管这取决于该库是否共享,使用了多少库,以何种方式以及内联是否在特定情况下是决定性的胜利。

内联对于优化如此重要的原因不是因为内联本身有很大的提升,而是由于不断传播和进一步优化的机会而打开。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.