标头库的好处

Neb*_*Fox 83 c++ header-only

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

Dav*_*men 57

标头库的好处:

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

仅限标头库的缺点:

  • 更大的目标文件.来自某个源文件中使用的库的每个内联方法也将在该源文件的编译对象文件中获得一个弱符号,即行外定义.这会降低编译器的速度并降低链接器的速度.编译器必须生成所有膨胀,然后链接器必须将其过滤掉.

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

  • 更纠结的编译.由于只有标头库#include所需的额外内容,您可以使用仅限标头库获得更多依赖项.更改库中某些键函数的实现,您可能需要重新编译整个项目.在编译库的源文件中进行更改,您只需重新编译一个库源文件,使用新的.o文件更新已编译的库,然后重新链接应用程序.

  • 让人难以阅读.即使有最好的文档,库的用户也经常不得不求助于阅读库的标题.标题库中的标题填充了实现细节,这些细节会妨碍理解界面.使用已编译的库,您所看到的只是界面和对实现操作的简要评论,而这通常是您想要的.这真的是你应该想要的.您不必了解实现细节以了解如何使用库.

  • 最后一点并没有多大意义.任何合理的文档都将包括函数声明,参数,返回值等..以及所有相关的注释.如果必须引用头文件,则文档失败. (21认同)
  • @Thomas - 即使拥有最好的专业图书馆,我也经常发现自己不得不求助于阅读"精美"标题.事实上,如果从代码和评论中提取所谓的"精细"文档,我通常喜欢阅读标题.代码加上评论告诉我的不仅仅是自动生成的文档. (6认同)
  • @Thomas:我不同意.头文件通常是我去文档的第一个地方.如果标题写得很好,通常不需要外部文档. (3认同)
  • 最后一点无效。头部已经在私有成员中填充了实现细节,所以它不像 cpp 文件隐藏了所有的实现细节。此外,像 C# 这样的语言在设计上本质上是“仅标头”,IDE 会处理模糊细节(“折叠”它们) (2认同)
  • @Tomas:同意,最后一点完全是虚假的.您可以轻松地将接口和实现与仅包含头的库分开; 你只需要接口标题#include实现细节.这就是为什么Boost库通常包含一个名为`detail`的子目录(和命名空间). (2认同)

Luc*_*ore 50

有些情况下,只有标题库是唯一的选项,例如在处理模板时.

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

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

当然,这适用于个案.例如,仅标题库有时会增加代码大小& 编译时间.

  • "拥有一个仅限标头的库也意味着您不必担心可能使用该库的不同平台":仅当您不必维护库时.否则,这是一场噩梦,错误报告说您无法复制或测试您拥有的材料. (4认同)
  • 我刚刚问了一个关于仅标头的性能优势的类似问题。如您所见,代码大小没有区别。但是,示例仅标头实现的运行速度慢了 7%。http://stackoverflow.com/questions/12290639/quantifiable-metrics-benchmarks-on-the-usage-of-header-only-c-libraries/13593041#13593041 (3认同)
  • @LuchianGrigore 我也找不到其他人。这就是为什么花了一段时间才回答。有很多推测性的“增加代码大小”和“内存消耗”评论。我终于有一个差异的快照,即使它只是一个例子。 (2认同)

Phi*_*erg 12

我知道这是一个旧线程,但没有人提到过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兼容的).

然而,上面提到的负面因素,例如编译时间增加.此外,您可能正在经营一家公司,因此您可能不希望将所有源代码实施细节交给所有用户,以防其中一个用户窃取它.


Jam*_*nze 8

主要的"好处"是它需要您提供源代码,因此您最终会得到机器上的错误报告以及您从未听说过的编译器.当库完全是模板时,您没有太多选择,但是当您有选择时,标题通常是一个糟糕的工程选择.(另一方面,当然,标题只表示您不必记录任何集成过程.)