内联还有用吗?

Is there still a use for inline?

我相信,inline 已经过时了,因为我读过 here:

No matter how you designate a function as inline, it is a request that the compiler is allowed to ignore: the compiler might inline-expand some, all, or none of the places where you call a function designated as inline.

不过,Angew seems to understand something I don't. In 他和我来来去去,就inline是否还有用

这个问题不是关于:

的问题

请记住,编译器可以随意inline,因此inline在那里没有帮助:哪里可以使用inline强制,不建议,编译代码的变化?

由于您所述的原因,

inline 现在主要只是一个外部 linkage 说明符。

所以是的,它确实有用,但它与实际内联函数不同。它允许您在编译单元之间多次定义相同的方法,并将它们正确地link在一起,而不是得到多个定义错误。

//header.h
inline void foo() {}
void goo() {}

//cpp1.cpp
#include "header.h"

//cpp2.cpp
#include "header.h"

// foo is okay, goo breaks the one definition rule (ODR)

实际上强制函数内联取决于编译器,有些可能通过特定的 attributes 或 pragmas 或 (__forceinline) 或诸如此类的东西提供支持。

简单地说,它允许您在 headers 中定义函数而不破坏 ODR...

我会尽力解释我的 "secret understanding"。

这里有两个完全不同的概念。一是编译器能够通过直接在调用点重复函数体来替换函数调用。另一种是在多个翻译单元(=多个 .cpp 文件)中定义函数的可能性。

第一个叫做函数内联。第二个是 inline 关键字的用途。 历史上, inline 关键字也是对编译器的强烈建议,它应该内联标记为 inline 的函数。随着编译器在优化方面变得更好,这个功能已经消退,并且使用 inline 作为内联函数的建议确实已经过时了。如果编译器发现更好的优化,它会很乐意忽略它并完全内联其他内容。

我希望我们已经处理了显式 inline– 内联关系。当前代码中有none。

那么,inline 关键字的实际用途是什么?很简单:标记为 inline 的函数可以在多个翻译单元中定义,而不会违反单一定义规则 (ODR)。想象一下这两个文件:

file1.cpp

int f() { return 42; }

int main()
{ return f(); }

file2.cpp

int f() { return 42; }

这个命令:

> gcc file1.cpp file2.cpp

会产生链接器错误,抱怨符号f被定义了两次。

但是,如果你用inline关键字标记一个函数,它会明确告诉编译器和链接器:"You guys make sure that multiple identical definitions of this function do not result in any errors!"

因此以下内容将起作用:

file1.cpp

inline int f() { return 42; }

int main()
{ return f(); }

file2.cpp

inline int f() { return 42; }

将这两个文件编译和链接在一起不会产生任何链接器错误。

请注意,f 的定义当然不必一字不差地出现在文件中。它可以来自 #included 头文件:

f.hpp

inline int f() { return 42; }

file1.cpp

#include "f.hpp"

int main()
{ return f(); }

file2.cpp

#include "f.hpp"

基本上,要将函数定义写入头文件,必须将其标记为inline,否则会导致多个定义错误。


最后一个难题是:为什么与内联无关的关键字实际上拼写为 inline?原因很简单:要内联一个函数(即通过在调用点重复函数体来替换对它的调用),编译器必须首先 拥有 函数体.

C++ 遵循单独的编译模型,其中 编译器 除了当前正在生成的目标文件外,无法访问其他目标文件。因此,为了能够内联一个函数,它的定义必须是当前翻译单元的一部分。如果你想在多个翻译单元中内联它,它的定义必须在所有的翻译单元中。通常,这会导致多重定义错误。所以如果你把你的函数放在一个头文件中并且 #include 它的定义随处可见以在任何地方启用它的内联,你必须将它标记为 inline 以防止多个定义错误。

请注意,即使在今天,虽然编译器会内联任何认为合适的函数,但它仍然必须能够访问该函数的定义。因此,虽然 inline 关键字不是提示 "please inline this," 所必需的,但您可能仍然会发现您需要使用它来 启用 编译器在选择时进行内联这样做。没有它,您可能无法将定义放入翻译单元,没有定义,编译器根本无法内联函数。

编译器不能。链接器可以。现代优化技术包括 Link-Time Code Generation (a.k.a. Whole Program Optimisation),其中优化器在实际链接之前作为链接过程的一部分对所有目标文件进行 运行。在此步骤中,所有函数定义当然可用,内联是完全可能的,而无需在程序的任何地方使用单个 inline 关键字。但是这种优化通常在构建时间上是昂贵的,特别是对于大型项目。考虑到这一点,仅依靠 LTCG 进行内联可能不是最佳选择。


为了完整起见:我在第一部分中略有作弊。 ODR 属性 实际上不是 inline 关键字的 属性,而是 内联函数 (这是语言的一个术语)。内联函数的规则是:

  • 可以在多个翻译单元中定义而不会导致链接器错误
  • 必须在使用它的每个翻译单元中定义
  • 其所有定义必须是令牌对令牌和实体对实体相同

inline 关键字将函数转换为内联函数。将函数标记为内联的另一种方法是直接在 class 定义中定义(而不仅仅是声明)它。这样的函数是自动内联的,即使没有 inline 关键字。