内联仍然有用吗?

Jon*_*Mee 85 c++ methods inline function deprecated

我相信,inline已经过时,因为我在这里读到:

无论你如何指定一个函数inline,它都是一个允许编译器忽略的请求:编译器可能内联扩展一些,全部或没有你调用指定函数的地方inline.

然而,Angew似乎理解我不知道的事情.在这个问题中他和我来回走了很多,关于是否inline仍然有用.

这个问题不是一个问题:

请记住,编译器可以随意使用inline,因此inline在那里没有用处:可以inline用来强制而不是建议编译代码的更改?

Rei*_*ica 77

我会尝试以最好的方式解释我的"秘密理解".

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

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

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

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

file1.cpp

int f() { return 42; }

int main()
{ return f(); }
Run Code Online (Sandbox Code Playgroud)

file2.cpp

int f() { return 42; }
Run Code Online (Sandbox Code Playgroud)

这个命令:

> gcc file1.cpp file2.cpp
Run Code Online (Sandbox Code Playgroud)

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

但是,如果使用inline关键字标记函数,它会特别告诉编译器和链接器:"你们确保这个函数的多个相同定义不会导致任何错误!"

所以以下内容将起作用:

file1.cpp

inline int f() { return 42; }

int main()
{ return f(); }
Run Code Online (Sandbox Code Playgroud)

file2.cpp

inline int f() { return 42; }
Run Code Online (Sandbox Code Playgroud)

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

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

f.hpp

inline int f() { return 42; }
Run Code Online (Sandbox Code Playgroud)

file1.cpp

#include "f.hpp"

int main()
{ return f(); }
Run Code Online (Sandbox Code Playgroud)

file2.cpp

#include "f.hpp"
Run Code Online (Sandbox Code Playgroud)

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


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

C++遵循单独的编译模型,其中编译器不能访问除当前生成的目标文件之外的目标文件.因此,为了能够内联函数,其定义必须是当前翻译单元的一部分.如果您希望能够在多个翻译单元中对其进行内联,则其定义必须包含在所有翻译单元中.通常,这会导致多重定义错误.因此,如果将函数放在标题中并将#include其定义放在任何地方以使其在任何地方都能内联,则必须将其标记为inline防止出现多个定义错误.

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

编译器不能.链接器可以.现代优化技术包括链接时代码生成(也称为整个程序优化),其中优化器在实际链接之前作为链接过程的一部分在所有目标文件上运行.在此步骤中,所有函数定义当然都是可用的,并且如果没有inline在程序中的任何位置使用单个关键字,则完全可以进行内联.但是这种优化在构建时通常很昂贵,特别是对于大型项目.考虑到这一点,完全依靠LTCG进行内联可能不是最好的选择.


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

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

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

  • OTOH,目前仔细的内联手动调整仍然可以击败编译器(参见[Andrei Alexandrescu在2014年CppCon上的演讲](https://www.youtube.com/watch?v=Qq_WaiwzOtI)).但是,只有当你是Facebook或那种规模的东西时,小的性能提升才有意义,其中0.3%的性能损失是一个严重的倒退. (6认同)
  • 答案的最后部分有很好的解释! (3认同)
  • 很好的解释,我仍然使用内联函数很多时候被程序调用. (2认同)
  • @PravasiMeet不,它们实际上是相同的功能,这就是重点.它在所有`.cpp`文件(=转换单元)中具有相同的地址,并共享相同的静态变量(如果有的话).它只是一个函数,但它的主体必须存在于所有引用它的翻译单元中(例如,调用它或取其地址). (2认同)
  • @Angew:换句话说,为了*潜在地*内联在多个`.cpp`文件(翻译单元)中调用的函数(在调用站点上用它的主体替换它的调用),编译器需要每个`.cpp`中的函数定义` 文件,但是链接器只允许在程序中定义一个函数(*一个定义规则*),所以我们使用`inline`关键字来绕过链接器的限制。底线:当在多个 .cpp 文件中调用函数时,`inline` 关键字使 *linker* 安静,以允许函数内联。我没听错吗? (2认同)
  • 解释它如何以及为什么与使用 `static` 不同会很有用。问题是,你从一个相当无用的重复定义的例子开始,这表明重点是在不同的地方定义不同的代码——这根本不适用于“内联”。我建议从这与使用 `static` 关键字不同的一点开始。值得指出的是,现代链接器能够内联。 (2认同)

Luc*_*ore 45

inline 由于你说的原因,现在大多只是一个外部链接说明符.

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

//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)
Run Code Online (Sandbox Code Playgroud)

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

简而言之,它允许您在不破坏ODR的情况下在标题中定义函数...

  • @supercat我不确定我是否在关注您...创建一个单独的问题并自己回答这个问题对您来说是否过多?我认为您所谈论的示例将非常有帮助,因为我不确定我是否理解“弱定义”,因此接下来的一切都变得越来越模糊。 (2认同)