内联还有用吗?

2021-12-07 00:00:00 function methods inline deprecated c++

我相信,inline 已经过时了,因为我阅读了这里:

<块引用>

无论您如何将函数指定为 inline,编译器都可以忽略该请求:编译器可能会内联扩展您调用的部分、全部或不扩展指定为 inline 的函数.

然而,Angew 似乎理解了一些我不理解的东西.在这个问题中他和我来回走了不少弯路,关于inline是否还在有用.

这个问题不是关于:

的问题
  • inline 的历史使用或 inline 仍可用于向编译器提示inline 函数的情况:我应该什么时候为函数/方法编写关键字内联"?.
  • 内联函数代码的优点或缺点:C++ 中内联函数的优点?
  • 强制编译器内联函数代码:在其他翻译单元中强制内联函数

记住编译器可以随意inline,所以inline在那里没有帮助:哪里可以使用inline强制,不建议,对编译代码进行更改?

解决方案

我会尽我所能解释我的秘密理解".

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

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

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

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

file1.cpp

int f() { return 42;}int main(){ 返回 f();}

file2.cpp

int f() { return 42;}

这个命令:

>gcc file1.cpp file2.cpp

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

然而,如果你用 inline 关键字标记一个函数,它会专门告诉编译器 &链接器:你们确保这个函数的多个相同定义不会导致任何错误!"

所以以下将起作用:

file1.cpp

inline int f() { return 42;}int main(){ 返回 f();}

file2.cpp

inline int f() { return 42;}

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

请注意,f 的定义当然不必逐字记录在文件中.它可以来自 #included 头文件:

f.hpp

inline int f() { return 42;}

file1.cpp

#include "f.hpp"int main(){ 返回 f();}

file2.cpp

#include "f.hpp"

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

<小时>

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

C++ 遵循一种单独的编译模型,其中 编译器 无法访问当前生成的对象文件以外的对象文件.因此,为了能够内联一个函数,它的定义必须是当前翻译单元的一部分.如果您希望能够将其内联在多个翻译单元中,则其定义必须包含在所有翻译单元中.通常,这会导致多重定义错误.所以如果你把你的函数放在一个header中并且#include它的定义无处不在以在任何地方启用它的内联,你必须将它标记为inline以防止多个定义错误.>

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

编译器不能.链接器可以.现代优化技术包括链接时代码生成(又名整个程序优化),在实际链接之前,优化器作为链接过程的一部分在所有目标文件上运行.在这一步中,所有函数定义当然都可用,并且内联完全可能,无需在程序中的任何地方使用单个 inline 关键字.但是这种优化通常在构建时间上成本很高,尤其是对于大型项目.考虑到这一点,仅依靠 LTCG 进行内联可能不是最佳选择.

<小时>

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

  • 可以在多个翻译单元中定义而不会导致链接器错误
  • 必须在使用它的每个翻译单元中定义
  • 它的所有定义必须是 token-for-token 和 entity-for-entity 相同的

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

I believed, inline was obsolete because I read 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.

However, Angew seems to understand something I don't. In this question he and I go back and forth quite a bit, on whether inline is still useful.

This question is not a question on:

  • The historical use of inline or where inline could still be used to hint to the compiler to inline functions: When should I write the keyword 'inline' for a function/method?.
  • The benefits or drawbacks of inlining function code: Benefits of inline functions in C++?
  • Forcing the compiler to inline function code: force inline function in other translation unit

Bearing in mind that the compiler can inline at will, so inline is not helpful there: Where can inline be used to force, not suggest, a change in compiled code?

解决方案

I will try to explain my "secret understanding" the best way I can.

There are two entirely separate concepts here. One is the compiler's ability to replace a function call by repeating the function body directly at the call site. The other is the possibility of defining a function in more than one translation unit (= more than one .cpp file).

The first one is called function inlining. The second is the purpose of the inline keyword. Historically, the inline keyword was also a strong suggestion to the compiler that it should inline the function marked inline. As compilers became better at optimising, this functionality has receded, and using inline as a suggestion to inline a function is indeed obsolete. The compiler will happily ignore it and inline something else entirely if it finds that's a better optimisation.

I hope we've dealt with the explicit inline–inlining relationship. There is none in current code.

So, what is the actual purpose of the inline keyword? It's simple: a function marked inline can be defined in more than one translation unit without violating the One Definition Rule (ODR). Imagine these two files:

file1.cpp

int f() { return 42; }

int main()
{ return f(); }

file2.cpp

int f() { return 42; }

This command:

> gcc file1.cpp file2.cpp

Will produce a linker error, complaining that the symbol f is defined twice.

However, if you mark a function with the inline keyword, it specifically tells the compiler & linker: "You guys make sure that multiple identical definitions of this function do not result in any errors!"

So the following will work:

file1.cpp

inline int f() { return 42; }

int main()
{ return f(); }

file2.cpp

inline int f() { return 42; }

Compiling and linking these two files together will not produce any linker errors.

Notice that of course the definition of f doesn't have to be in the files verbatim. It can come from an #included header file instead:

f.hpp

inline int f() { return 42; }

file1.cpp

#include "f.hpp"

int main()
{ return f(); }

file2.cpp

#include "f.hpp"

Basically, to be able to write a function definition into a header file, you have to mark it as inline, otherwise it will lead to multiple definition errors.


The last piece of the puzzle is: why is the keyword actually spelled inline when it has nothing to do with inlining? The reason is simple: to inline a function (that is, to replace a call to it by repeating its body on the call site), the compiler must have the function's body in the first place.

C++ follows a separate compilation model, where the compiler doesn't have access to object files other than the one it's currently producing. Therefore, to be able to inline a function, its definition must be part of the current translation unit. If you want to be able to inline it in more than one translation unit, its definition has to be in all of them. Normally, this would lead to a multiple definition error. So if you put your function in a header and #include its definition everywhere to enable its inlining everywhere, you have to mark it as inline to prevent multiple definition errors.

Notice that even today, while a compiler will inline any function is sees fit, it must still have access to that function's definition. So while the inline keyword is not required as the hint "please inline this," you may still find you need to use it to enable the compiler to do the inlining if it chooses to do so. Without it, you might not be able to get the definition into the translation unit, and without the definition, the compiler simply cannot inline the function.

The compiler cannot. The linker can. Modern optimisation techniques include Link-Time Code Generation (a.k.a. Whole Program Optimisation), where the optimiser is run over all object files as part of the linking process, before the actual linking. In this step, all function definitions are of course available and inlining is perfectly possible without a single inline keyword being used anywhere in the program. But this optimisation is generally costly in build time, especially for large projects. With this in mind, relying solely on LTCG for inlining may not be the best option.


For completeness: I've cheated slightly in the first part. The ODR property is actually not a property of the inline keyword, but of inline functions (which is a term of the language). The rules for inline functions are:

  • Can be defined in multiple translation units without causing linker errors
  • Must be defined in every translation unit in which it is used
  • All its definitions must be token-for-token and entity-for-entity identical

The inline keyword turns a function into an inline function. Another way to mark a function as inline is to define (not just declare) it directly in a class definition. Such a function is inline automatically, even without the inline keyword.

相关文章