为啥在 C++ 中没有强制内联的标准方法?
Posted
技术标签:
【中文标题】为啥在 C++ 中没有强制内联的标准方法?【英文标题】:Why there is no standard way to force inline in C++?为什么在 C++ 中没有强制内联的标准方法? 【发布时间】:2011-05-24 09:35:17 【问题描述】:根据wikipedia C++ article
C++ 旨在为程序员提供选择,即使这可能导致程序员选择错误。
如果它是这样设计的,为什么没有标准的方法来强制编译器内联某些东西,即使我可能错了?
或者我可以问为什么inline
关键字只是一个提示?
我想我在这里别无选择。
在 OOP 世界中,我们在对象上调用方法,应该避免直接访问成员。如果我们不能强制内联访问器,那么我们就无法编写高性能但仍可维护的应用程序。
(我知道许多编译器都实现了自己的强制内联方式,但这很丑陋。使用宏在类上创建内联访问器也很丑陋。)
编译器总是比程序员做得更好吗?
【问题讨论】:
编译器做了一些疯狂的优化工作,内联普通访问器是基础的基础。 你有什么理由相信你可以选择比编译器更好的选择?它对 CPU 指令排序、流水线停顿等方面的了解比您可能了解的多得多。 关于你的最后一个问题,几乎总是...... 没有标准的方法,因为没有办法强制内联。 (为什么没有标准方法让猪飞起来?)。在某些情况下,无论您强制执行多少次,编译器都无法内联函数。 @SoapBox:找到内联有帮助的情况并不难,但编译器没有检测到它。经常发生在我身上。编译器编写者(和他们的编译器)并不愚蠢,但提出这些问题的人也不愚蠢。 【参考方案1】:编译器如何内联递归函数(特别是如果编译器不支持尾调用优化,即使支持,该函数也不支持尾调用优化)。
这只是编译器应该决定内联是否实用的原因之一。可能还有其他我现在想不起来的。
【讨论】:
特别是如果函数不能被尾调用优化:) @Matthieu M:现在看起来怎么样 :) 另一种情况是函数指针。尽管编译器可以为此生成一个外联副本并将其内联到其他地方。【参考方案2】:编译器总是比程序员做得更好吗?
不,并非总是如此……但程序员更容易出错,并且不太可能在几年内保持最佳调优。底线是,内联仅在函数非常小的情况下(对于至少一个常见/重要的代码路径)有助于提高性能,但它可以帮助大约一个数量级,当然这取决于许多事情。程序员评估通常是不切实际的,更不用说仔细观察函数的重要性了,阈值可能会因编译器实现选择、命令行选项、CPU 模型等而有所不同。有很多事情可能会突然膨胀函数 - 任何非内置类型都可以触发各种不同的行为(尤其是在模板中),运算符的使用(甚至new
)可以重载,调用约定和异常处理步骤的冗长通常对程序员。
如果编译器没有内联一些小到足以让您期待有用的性能改进的东西,那么编译器可能会意识到一些实现问题,而您实际上并没有使情况变得更糟。在那些灰色情况下,编译器可能会采用任何一种方式,而您刚刚超过某个阈值,那么性能差异无论如何都不太可能显着。
此外,一些程序员(包括我自己)可能很懒惰并故意滥用 inline
作为将实现放在头文件中的便捷方式,绕过 ODR,即使他们知道这些功能很大,如果编译器(被要求)实际上内联它们,那将是灾难性的。不过,这并不排除强制内联关键字/符号……它只是解释了为什么很难改变围绕当前 inline
关键字的期望。
【讨论】:
我不认为这是对关键字的滥用。 @GMan, @Matthieu: 鉴于inline
肯定是为了传达内联的希望或期望而引入的,那么纯粹使用它来避免 ODR 对我来说肯定是一种滥用 - 并不意味着我时不时也没有这样做... :-)。甚至可能是这种令人毛骨悚然的滥用是关键字现在放松的“如果编译器喜欢它”影响的主要原因......?
什么? inline
不是因为 ODR 而专门添加的吗?
@TonyD:我以为是为了声明代码内联在标题中而添加的。
@MooingDuck:在现代 C++ 中,它具有用于此目的的实用程序 - 允许编译器区分有意的离线调用 - 在链接时解决 - 和意外遗漏的定义保证 C.T.错误,但在最初由 extern
关键字控制的 C/C++ 的演变中,首先使用宏进行内联,然后 inline
修复了类型安全、副作用、缺乏递归和宏的其他弊端。 【参考方案3】:
或者我可以问为什么 inline 关键字是 只是提示?
因为你“可能”比编译器更了解。
大多数时候,对于未标记为内联(并且正确声明/定义)的函数,编译器将根据其配置和实现自行评估该函数是否可以内联。
例如,如果代码不长和/或太复杂,大多数编译器会自动内联在头文件中完全定义的成员函数。那是因为函数在标题中可用,为什么不尽可能多地内联它呢? 但是这不会发生,例如在 Visual Studio 的调试模式下:在调试中,调试信息仍然需要映射函数的二进制代码,因此它避免内联,但仍会内联标记为内联的函数,因为用户需要它。如果你想标记函数你不需要有调试时信息(如简单的getter),同时在调试时获得更好的性能,这很有用。 在发布模式下(默认情况下),编译器会积极内联它所能做的一切,即使你激活了调试信息,也很难调试代码的某些部分。
因此,一般的想法是,如果您以有助于编译器内联的方式进行编码,它将尽可能多地内联。如果您以难以或不可能内联的方式编写代码,它将避免。如果您将某些内容标记为内联,您只需告诉编译器,如果它发现很难内联但并非不可能内联,则应将其内联。
由于内联取决于调用者和被调用者的上下文,因此没有“规则”。 通常建议的是简单地忽略显式内联标记函数,但在两种情况下:
-
如果您需要将函数定义放在头文件中,只需内联即可;模板(成员或非成员)函数和其他只是快捷方式的实用函数通常是这种情况;
如果您希望特定编译器在编译时以特定方式运行,例如,即使在 Visual Studio 编译器的调试配置中也将某些成员函数标记为内联。
编译器总是做得更好吗 比程序员?
不,这就是为什么有时使用 inline 关键字会有所帮助。程序员有时可以比编译器更好地了解什么是必要的。例如,如果程序员希望它的二进制文件尽可能小,取决于代码,内联可能是有害的。在需要速度性能的应用程序中,积极地内联会很有帮助。编译器如何知道需要什么?必须对其进行配置并允许以细粒度的方式了解真正想要内联的内容。
【讨论】:
【参考方案4】:错误的假设。
有一种方法。拼写为#define
。对于许多早期的 C 项目来说,这已经足够了。 inline
完全不同 - 提示,更好的语义 - 它可以添加到宏之外。但是,一旦两者兼而有之,就没有第三个选项的余地了,一个语义更好但不是可选的选项。
【讨论】:
【参考方案5】:如果你真的需要强制内联函数(为什么?),你可以这样做:复制代码并粘贴,或者使用宏。
【讨论】:
以上是关于为啥在 C++ 中没有强制内联的标准方法?的主要内容,如果未能解决你的问题,请参考以下文章