为什么C++中没有强制使用内联函数的标准方法?

21
根据维基百科C++文章的说法:

C++旨在给程序员提供选择,即使这使得程序员可能会做出错误的选择。

既然设计如此,为什么没有一种标准的方式来强制编译器内联某些内容,即使我可能是错的呢?
或者我可以问,为什么`inline`关键字只是一个提示?
我想这里我没有选择。
在面向对象编程中,我们对对象调用方法,并应避免直接访问成员。如果我们无法强制访问器内联,则无法编写高性能但仍可维护的应用程序。
(我知道许多编译器实现了自己的强制内联方式,但这很丑陋。使用宏使类上的内联访问器也很丑陋。)
编译器总是比程序员做得更好吗?

7
编译器会做一些疯狂的优化,内联简单的访问器是基础中的基础。 - Xeo
4
你有什么理由相信你能比编译器做出更好的选择呢?它对CPU指令排序、流水线停顿等方面的了解远比你多。 - SoapBox
5
关于你最后一个问题,几乎总是... - Nim
3
没有标准方法,因为没有办法强制内联(为什么没有标准方法让猪飞?)。有时候,即使你强制多次,编译器也可能无法将函数内联。 - sehe
3
@SoapBox:找到需要使用内联的情况并不难,但编译器可能无法检测到这一点。我经常会遇到这种情况。编译器的编写者(及其编译器)并不愚蠢,但提出这些问题的人也不是。 - user541686
显示剩余4条评论
5个回答

18

如果编译器不支持尾调用优化,甚至在支持时函数也无法进行尾调用优化,那么编译器将如何内联递归函数。

这只是编译器决定是否实现内联操作的原因之一。还可能有其他原因,但我暂时想不到。


特别是如果函数无法进行尾调用优化 :) - Matthieu M.
@Matthieu M: 现在看起来怎么样呢 :) - Aamir
4
另一种情况是函数指针。尽管编译器可以为此生成一个不在原地的副本并将其内联到其他地方。 - IronMensan

5
编译器总是比程序员更优秀吗?不总是,但程序员会犯更多的错误,并且在多年的调优过程中,他们不太可能保持最佳状态。结果很简单,只有当函数真的很小(至少在一个常见/重要的代码路径上)时,内联才有助于性能,但这取决于许多因素,当然也可能会提高一个数量级。对于程序员来说,评估甚至仅关注一个微不足道的函数通常是不切实际的,而且阈值可能随编译器实现选择、命令行选项、CPU模型等变化。有很多事情可能会突然使函数变得膨胀——任何非内置类型都可能触发各种不同的行为(尤其是在模板中),使用操作符(甚至包括 new )可能会被重载,调用约定的冗长以及异常处理步骤通常对程序员来说并不明显。如果编译器没有内联某个足够小以期望它内联后性能得到改善的东西,那么它就意识到了一些你不知道的实现问题,这会导致性能更差。在那些灰色区域里,编译器可能会走两条路,而你只是超过了某个阈值,那么性能差异也不太可能显著。此外,一些程序员(包括我自己)可以懒惰地滥用 inline,这只是一个方便的方法来将实现放在头文件中,绕过 ODR,即使他们知道这些函数很大,并且如果编译器真的内联它们,会带来灾难性的后果。这并不排除强制内联关键字/符号的存在... 这只是解释为什么很难改变当前 inline 关键字的预期。

1
我不认为那是对关键字的滥用。 - GManNickG
@GMan,@Matthieu:鉴于“inline”明确引入以传达内联的希望或期望,那么仅为避免ODR而使用它似乎对我来说是一种滥用——这并不意味着我没有偶尔这样做过... :-)。甚至可能是这种潜在的滥用是现在放松了关键字影响的主要原因...? - Tony Delroy
1
什么?inline不是因为ODR而被特别添加的吗? - Thomas Eding
@TonyD:我认为加上这个声明是为了表明代码是内联在头文件中的。 - Mooing Duck
@MooingDuck:在现代C++中,它具有实用性,允许编译器区分有意的离线调用-在链接时解决-和意外省略的定义引起的C.T.错误,但在C/C++的演变中,最初由“extern”关键字控制,然后使用宏首先进行内联,然后是“inline”,修复了宏的类型安全、副作用、递归缺失和其他问题。 - Tony Delroy

3
因为你“可能”比编译器更了解情况。
大多数情况下,对于未标记为inline(并正确声明/定义)的函数,编译器会根据其配置和实现自行评估函数是否可以内联。例如,如果成员函数在头文件中完全定义,则大多数编译器将自动内联该函数,如果代码不太长或过于复杂。这是因为该函数在头文件中可用,为什么不尽可能地内联呢?但是,在Visual Studio的调试模式下不会发生这种情况:在调试模式下,调试信息仍然需要映射函数的二进制代码,因此它会避免内联,但仍会内联标记为inline的函数,因为用户要求它这样做。如果您想标记不需要具有调试时间信息的函数(例如简单的getter),同时在调试时获得更好的性能,则这非常有用。在发布模式(默认情况下),编译器将积极地内联所有可能的内容,即使您激活了调试信息,也会使某些代码部分更难调试。
因此,总体思路是,如果您以一种有助于编译器内联的方式编写代码,它将尽可能地内联。如果您以难以或不可能内联的方式编写代码,则会避免内联。如果您标记了某些内容为inline,则只需告诉编译器,如果它发现很难但不是不可能内联,则应将其内联。
由于内联取决于调用方和被调用方的上下文,因此没有“规则”。通常建议的是,除非在两种情况下,否则简单地忽略显式标记函数inline:
1. 如果您需要将函数定义放在头文件中,则必须内联;这通常是模板(成员或非成员)函数和其他仅为快捷方式的实用程序函数的情况; 2. 如果您希望特定的编译器在编译时以特定的方式运行,例如标记一些成员函数为inline,即使在Visual Studio编译器的调试配置中也要进行内联。
不,这就是为什么有时使用inline关键字会有所帮助。程序员有时可以比编译器更好地了解情况。例如,如果程序员希望其二进制文件尽可能小,那么根据代码,内联可能会有害。在需要速度性能的应用程序中,积极内联可能会非常有帮助。编译器如何知道需要什么?它必须进行配置,并允许以细粒度的方式了解真正想要内联的内容。

0

错误的假设。

有一种方法。它被拼写为#define。对于许多早期的C项目来说,这已经足够了。inline是足够不同的 - 提示,更好的语义 - 它可以在宏的基础上添加。但是一旦你同时拥有了两者,就很少有第三个选项的余地,介于两者之间,具有更好的语义但是不可选。


-1
如果你真的需要强制将函数内联(为什么要这样做?),你可以这样做:复制代码并粘贴,或者使用宏。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接