激励'inline'说明符的真实世界示例?

Motivating real world examples of the 'inline' specifier?

本文关键字:真实世界 说明符 inline 激励      更新时间:2023-10-16

背景:C++内联关键字不确定函数是否应该内联。

相反,内联允许您为单个函数或变量提供多个定义,只要每个定义出现在不同的翻译单元中。

基本上,这允许在头文件中定义全局变量和函数。

有没有一些例子可以说明我为什么要在头文件中编写定义?

我听说可能有一些模板示例,其中不可能在单独的cpp文件中编写定义。

我听说过其他关于性能的说法。但这真的是真的吗?因为,据我所知,内联关键字的使用并不能保证函数调用是内联的(反之亦然)。

我有一种感觉,这个特性可能主要是由试图编写古怪和高度优化的实现的库作者使用的。但是有一些例子吗?

实际上很简单:当你想在头中写一个定义(函数或变量(从c++17开始))时,你需要内联。否则,一旦你的标题包含在超过1个图内,你就会违反odr。仅此而已。

值得注意的是,有些实体是隐式内联声明的,比如:

  • 在类主体内部定义的方法
  • 模板函数和变量
  • constexpr函数和变量

现在的问题变成了为什么以及何时有人想要在头中编写定义,而不是将头中的声明和源代码文件中的定义分开。这种方法有优点也有缺点。以下是一些需要考虑的问题:

最优化

在源文件中具有该定义意味着函数的代码被烘焙到tu二进制文件中。它不能在定义它的tu之外的调用站点内联。将它放在头中意味着编译器可以在它认为合适的任何地方内联它。或者,它可以根据调用函数的上下文为函数生成不同的代码。在可执行文件或库中使用lto也可以实现同样的效果,但对于库,启用此优化的唯一选项是在标头中包含定义。

图书馆分布

除了在库中实现更多优化外,拥有一个仅包含头的库(如果可能的话)意味着分发该库的方法更简单。用户所要做的就是下载headers文件夹并将其添加到他/她的项目的include路径中。在非纯头库的情况下,事情会变得更加复杂。因为您不能混合和匹配由不同编译器编译的二进制文件,甚至不能由同一编译器编译但具有不同标志的二进制文件。因此,您必须分发带有完整源代码和构建工具的库,或者以多种格式编译库(cpu架构/OS/编译器/编译器标志组合)

人类偏好

有些人(包括我在内)认为必须编写一次代码是一种优势:无论是从代码文档的角度还是从维护的角度。其他人则认为将声明与定义分开更好。一个论点是,它实现了接口与实现的分离,但事实并非如此:在头中,您需要有私有成员声明,即使这些声明不是接口的一部分。

编译时性能

将所有代码都放在头中意味着在每个tu中都要复制它。这在编译时是一个真正的问题。重载头C++项目因编译速度慢而臭名昭著。这也意味着,对函数定义的修改将触发包括它的所有tu的重新编译,而在源代码中定义的情况下只有1 tu。预编译的头试图解决这个问题,但解决方案是不可移植的,并且有自己的问题。

如果同一个函数定义出现在多个编译单元中,则它需要是inline,否则会出现链接错误。

您需要inline关键字,例如,对于函数模板,如果您想使用标头使其可用,因为它们的定义也必须在标头中。

下面的语句可能有点过于简单了,因为编译器和链接器现在确实很复杂,但要了解基本概念,它仍然有效。

一个cpp文件和该cpp文件包含的头组成一个编译单元,每个编译单元都是单独编译的。在该编译单元中,编译器可以进行许多优化,比如潜在地内联任何函数调用(无论它是成员还是自由函数),只要代码仍然按照规范运行即可。

因此,如果将函数定义放在头中,编译器就可以知道该函数的代码,并可能进行更多优化。

如果定义在另一个编译单元中,则编译器不能做太多工作,并且只能在链接时进行优化。链路时间优化也是可能的,而且确实也是可行的。虽然链接时间优化变得更好,但它们可能做不到编译器能做的那么多

只有头的库有一个很大的优势,那就是你不需要提供项目文件,想要使用该库的人只需将头复制到他们的项目中并包含它们。

简而言之:

  1. 您正在编写一个库,并且希望它仅为标头,以便更方便地使用。

    即使它不是库,在某些情况下,您可能希望将一些定义保留在标头中,以便于维护(这是否会使事情变得更容易是主观的)。

据我所知,内联关键字的使用并不能保证函数调用是内联的

是的,在标头中定义它(作为inline)并不能保证内联。但是,如果没有在头中定义它,它将永远不会被内联(除非使用链接时间优化)。因此:

  1. 如果编译器决定内联函数,则希望它能够内联函数。

    它还可以让编译器了解更多关于函数的知识:

    • 也许它从不抛出,但没有标记为noexcept

    • 也许可以将几个连续的调用合并为一个(没有副作用等),但缺少__attribute__((const))

    • 也许它永远不会回来,但[[noreturn]]不见了;

    • 。。。

可能存在无法在单独的cpp文件中写入定义的模板示例。

大多数模板都是这样。它们会自动表现为inline,因此不需要显式指定。请参阅为什么模板只能在头文件中实现?详细信息。