仅标头库的好处


98

仅标头库的好处是什么?为什么要以与将实现放入单独文件相反的方式编写它?


通常是模板,但也将使其更易于分发和使用。
BoBTFish 2012年

4
我想将仅标头库的缺点添加到问题的范围内……
moooeeeep 2012年

有哪些未提及的不利之处?
NebulaFox 2012年

7
@moooeeeep:对于缺点,你可能需要阅读的段落“停止内联码”C ++该做什么和不该做什么的Chromium项目的网页。
C64先生2012年

Answers:


57

在某些情况下,仅标头库是唯一的选择,例如在处理模板时。

拥有仅标头的库也意味着您不必担心可能在哪个平台上使用该库。分离实现时,通常会隐藏实现细节,并将库作为标头和库(libdll.so文件)的组合进行分发。当然,必须为您提供支持的所有不同操作系统/版本编译这些文件。

您也可以分发实现文件,但这对用户来说意味着额外的步骤-在使用库之前对其进行编译。

当然,这视情况而定。例如,仅标头库有时会增加代码大小和 编译时间。


6
“拥有仅标头的库也意味着您不必担心可能在哪个平台上使用该库”:仅当您不必维护该库时。否则,这将是一场噩梦,错误报告将导致您无法复制或测试所拥有的材料。
詹姆斯·坎泽

1
我只是问了一个有关标头仅对性能有好处的类似问题。如您所见,代码大小没有区别。但是,示例仅标头实现的运行速度慢了7%。stackoverflow.com/questions/12290639/...
Homer6

@ Homer6感谢您对我的查询。我从来没有实际测量过。
Luchian Grigore 2012年

1
@LuchianGrigore我找不到任何其他人。这就是为什么花了一段时间回答的原因。有太多的推测性“增加代码大小”和“内存消耗”注释。我终于有了差异的快照,即使这只是一个例子。
Homer6

@ Homer6。为什么不增加代码大小?假设您创建了多个仅使用标头库的库,然后您的应用程序使用了所有这些库,那么您将必须具有多个副本,而不是针对单个共享库进行链接。
pooya13

60

仅标头库的优点:

  • 简化了构建过程。您不需要构建库,也不需要在构建的链接步骤中指定已编译的库。如果您有一个已编译的库,则可能要构建它的多个版本:一个已启用调试的编译,另一个已启用优化的编译,可能还有另一个剥离的符号。对于多平台系统,甚至更多。

仅标头库的缺点:

  • 更大的目标文件。在某些源文件中使用的库中的每个内联方法也将在该源文件的已编译目标文件中获得弱符号,脱机定义。这减慢了编译器的速度,也减慢了链接器的速度。编译器必须生成所有这些膨胀,然后链接程序必须将其过滤掉。

  • 更长的编译时间。除了上面提到的膨胀问题之外,编译将花费更长的时间,因为带有仅标头的库的标头本质上比编译的库大。对于使用该库的每个源文件,都将需要解析那些大的标头。另一个因素是,仅标头库中的那些标头文件必须具有#include内联定义所需的标头以及将库构建为编译库时所需的标头。

  • 更纠结的编译。由于仅标头库#include需要更多的依赖,因此仅标头库具有更多的依赖项。更改库中某些关键功能的实现,您很可能需要重新编译整个项目。在已编译库的源文件中进行更改,您要做的就是重新编译一个库源文件,使用该新的.o文件更新已编译的库,然后重新链接该应用程序。

  • 人类难以阅读。即使拥有最好的文档,图书馆的用户也常常不得不诉诸阅读图书馆的标头。仅标头库中的标头填充有实现细节,这些细节会妨碍您理解接口。使用已编译的库,您所看到的只是接口和关于实现功能的简要说明,而这通常就是您想要的。那才是您真正想要的。您不必知道实现细节即可知道如何使用该库。


21
最后一点没有任何意义。任何合理的文档都将包括函数声明,参数,返回值等。以及所有相关的注释。如果必须引用头文件,则说明文档失败。
汤玛斯(Thomas)

6
@Thomas-即使拥有最好的专业库,我也经常发现自己不得不诉诸“精细”标题。实际上,如果从代码和注释中提取了所谓的“精细”文档,我通常会喜欢阅读标头。与自动生成的文档相比,代码加上注释所提供的信息更多。
大卫·哈曼

2
最后一点无效。标头中已经在私有成员中填充了实现详细信息,因此,这不像cpp文件隐藏了所有实现详细信息。另外,像C#这样的语言在设计上实质上只是“仅标头”,而IDE则负责掩盖细节(“将其折叠”)
Mark Lakata

2
@Tomas:同意,最后一点完全是伪造的。您可以轻松地将接口和实现与仅标头库分离开来;您只需要接口头#include实现细节。这就是为什么Boost库通常包含一个名为的子目录(和名称空间)的原因detail
Nemo

4
@托马斯:我不同意。头文件通常是我首先去寻找文档的地方。如果标题写得很好,则通常不需要外部文档。
乔尔·科内特

14

我知道这是一个旧线程,但是没有人提到ABI接口或特定的编译器问题。所以我想我会的。

这基本上是基于这样的概念:您编写一个带有标头的库以分发给人们,或者重用自己,而不是将所有内容都包含在标头中。如果您想重用头文件和源文件,并在每个项目中重新编译它们,那么这实际上并不适用。

基本上,如果您编译C ++代码并使用一个编译器构建一个库,那么用户尝试将该库与其他编译器或同一编译器的不同版本一起使用,则由于二进制不兼容,您可能会遇到链接器错误或奇怪的运行时行为。

例如,编译器供应商经常在版本之间更改其STL的实现。如果您的库中有一个接受std :: vector的函数,则它希望该类中的字节以编译库时的排列方式进行排列。如果在新的编译器版本中,供应商对std :: vector进行了效率改进,则用户的代码将看到可能具有不同结构的新类,并将该新结构传递到您的库中。一切都从那里走下坡路...这就是为什么建议不要跨越库边界传递STL对象的原因。C运行时(CRT)类型也是如此。

在谈论CRT时,您的库和用户的源代码通常需要链接到同一CRT。在Visual Studio中,如果您使用多线程CRT构建库,但是用户针对多线程调试CRT进行链接,那么您将遇到链接问题,因为您的库可能找不到所需的符号。我不记得它是哪个函数,但是对于Visual Studio 2015,Microsoft内置了一个CRT函数。突然,它不在标头中,而在CRT库中,因此希望在链接时找到它的库不再能做,这会产生链接错误。结果是这些库需要使用Visual Studio 2015重新编译。

如果您使用Windows API,但使用与库用户不同的Unicode设置进行构建,则还会出现链接错误或奇怪的行为。这是因为Windows API具有使用Unicode或ASCII字符串的函数,以及根据项目的Unicode设置自动使用正确类型的宏/定义。如果跨库边界传递的字符串类型错误,则在运行时会中断。或者,您可能会发现该程序最初没有链接。

对于跨其他第三方库(例如本征向量或GSL矩阵)跨库边界传递对象/类型的情况,这些情况也适用。如果第三方库在您编译库和用户编译其代码之间更改了它们的标头,那么事情将会中断。

基本上,为了安全起见,您可以跨库边界传递的唯一内容是类型和普通旧数据(POD)。理想情况下,任何POD应该采用您自己的标头中定义的结构,并且不依赖任何第三方标头。

如果您提供仅标头的库,则所有代码都将使用相同的编译器设置和相同的标头进行编译,因此许多此类问题都将消失(假设您和您的用户使用的第三部分库的版本与API兼容)。

但是,上面已经提到了负面因素,例如增加了编译时间。另外,您可能正在经营一家公司,因此您可能不想将所有源代码实现的详细信息交给所有用户,以防万一其中一个用户偷了它。


8

主要的“好处”是它要求您提供源代码,因此最终将获得关于机器和从未听说过的编译器的错误报告。当库完全是模板时,您没有太多选择,但是当您选择时,仅标头通常是一个糟糕的工程选择。(当然,另一方面,标头仅意味着您不必记录任何集成过程。)


0

内联可以通过链接时间优化(LTO)完成

我想强调这一点,因为它降低了仅标头库的两个主要优点之一的值:“您需要在标头上进行内联定义”。

最小的具体示例如下所示:链接时优化和内联

因此,您只需传递一个标志,就可以在目标文件之间进行内联而不需要任何重构工作,而无需再在标头中保留定义。

LTO也可能有其自身的缺点:是否有理由不使用链接时间优化(LTO)?

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.