141

仅标头库有什么好处,为什么要以这种方式编写它而不是将实现放入单独的文件中?

4

5 回答 5

80

仅标头库的好处:

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

仅标头库的缺点:

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

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

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

  • 人类更难阅读。即使有最好的文档,图书馆的用户也经常不得不求助于阅读图书馆的标题。仅标头库中的标头充满了妨碍理解接口的实现细节。使用已编译的库,您所看到的只是接口和对实现功能的简短评论,而这通常就是您想要的。这就是你应该想要的。您不必知道实现细节就可以知道如何使用该库。

于 2012-10-01T13:04:41.287 回答
72

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

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

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

当然,这需要根据具体情况而定。例如,仅头文件库有时会增加代码大小 &编译时间。

于 2012-10-01T10:18:44.300 回答
24

我知道这是一个旧线程,但没有人提到 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 矩阵)跨库边界传递对象/类型。如果第 3 方库在您编译库和您的用户编译他们的代码之间更改了它们的标题,那么事情就会中断。

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

如果您提供仅标头库,那么所有代码​​都将使用相同的编译器设置和相同的标头进行编译,因此很多这些问题都会消失(提供您和您的用户使用的第三方库的版本与 API 兼容)。

但是,上面已经提到了一些负面因素,例如增加了编译时间。此外,您可能正在经营一家企业,因此您可能不想将所有源代码实现细节交给所有用户,以防他们中的一个人窃取它。

于 2016-09-29T09:59:29.150 回答
9

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

于 2012-10-01T12:27:35.170 回答
2

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

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

一个最小的具体示例显示在:Link-time optimization and inline

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

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

于 2020-08-28T16:53:33.853 回答