3

我在foo.h头文件中定义了以下类

class Foo {

public:
    inline int Method();

};

inline int Foo::Method() { // Implementation }

我现在想将实现移动到foo.cpp文件中。为此,我必须删除inline关键字并将方法的实现移动到这样的foo.cpp文件中

#include `foo.h`

inline int Foo::Method() { // Implementation }

我有两个问题:

  1. 我关于删除inline关键字的陈述是否正确?一定要删除吗?
  2. 删除inline关键字通常如何影响性能(实际上我所有的方法都是内联的)?

非常感谢您提前。

4

6 回答 6

16

如果您将函数定义从标头移动到 cpp 文件,则必须删除该函数的所有位置的inline关键字。使用较旧的链接器可能会使事情稍微变慢,但使用现代链接器,您应该注意到性能没有真正的差异。*

在某些情况下,公共成员函数可以是inline,但这只是个坏主意。不要这样做。可以进行参数将某些私有成员函数标记为inline,但实际上您真正想要的那些是__attribute__((always_inline))__forceinline

*在极其罕见的情况下,它会有所作为,但 99% 的情况下不会,剩下的 99.9% 的情况你都不在乎。如果测量结果显示您达到了万分之一,您可以使用上述__forceinline.

于 2013-06-11T21:55:33.217 回答
5

你和这里的人们就小功能提供建议,正在寻找inline老式的方法。

inline曾经的意思是“我希望这段代码快速运行,所以每当我调用这个函数时,我希望你就地扩展它以避免函数调用的开销。”

这是一个非常好的优化。事实上,这太好了,即使你指定inline.

编译器也可以自由地扩展您的inline功能。所以你真的不必担心它会如何影响性能,因为inline如果你以愚蠢的方式使用它,编译器可以并且会忽略它。

事实上,今天的编译器几乎总是忽略你对 . 的使用inline,只做他们认为最好的事情。

那么,知道了这一点,为什么人们仍然使用inline?

现在使用只有一个理由inline,那就是解决单一定义规则 (ODR)。

在 C/C++ 中,您只能定义一次函数。如果你这样做:

int foo() { /* do something */ }
int foo() { /* do something else */ }

编译器会抱怨你已经定义了两次相同的函数。

这看起来像一个愚蠢的例子,但是当你使用时特别容易做这样的事情#include- 如果你在一个标题中定义你的函数,并且你#include两次使用相同的标题,这正是你正在做的事情。

值得庆幸的是,inline它还有另一个今天仍然有效的用途:如果您将函数标记为inline,它会强制编译器消除 ODR 问题,从而可以在标头中定义您的函数。

换句话说,inline现在的意思是“我想在标题中定义这个函数”。

当您以这种方式看待它时,应该清楚inline在将函数移动到 cpp 文件时应该删除 。


出于兴趣,有几个地方隐式地使函数内联。其中之一是在类成员函数中:

struct Foo {
    void bar() { /* do something */ }
};

我见过人们标记这样inline的功能,但这完全是多余的。编译器无论如何都会这样做;无需担心 ODR,也不会获得任何性能。

另一个地方在模板中。由于模板必须在标头中定义,因此它们不受 ODR 的约束,并且inline对它们进行处理是多余的。

于 2013-06-11T23:22:33.083 回答
5

关键字inline在类中是多余的。如果你有一个函数体,这是隐含的。

在实现文件中它也是相当多余的。

它的唯一用途是在头文件(或类外部的成员函数,但在头文件中)定义一个自由函数以避免多个主体。

在现代编译器的优化方面,它甚至更加多余,它们无论如何都会毫无疑问地内联任何可见的东西,或者随意忽略您的关键字。

内联用法必须一致!从 7.1.2p4 开始:

内联函数应在使用它的每个翻译单元中定义,并且在每种情况下都应具有完全相同的定义(3.2)。[ 注意:在其定义出现在翻译单元中之前,可能会遇到对内联函数的调用。—尾注] 如果函数的定义在其第一次声明为内联之前出现在翻译单元中,则程序格式错误。如果具有外部链接的函数在一个翻译单元中声明为内联,则应在其出现的所有翻译单元中声明为内联;不需要诊断。...

于 2013-06-11T21:49:25.280 回答
4

如果函数不是 TINY(或者接受多个参数,但没有做太多事情,例如构造函数或类似的,需要一堆东西,然后将其复制到类内部的某个位置),则内联它几乎没有首先影响性能。Setter 和 getter 通常是内联的好选择,因为它们(通常)只是将数据从一个地方复制到另一个地方,并且可以在调用发生的地方轻松完成。

正如其他人所说,它是一个“请编译器,如果我可以问你,考虑内联这个函数” - 它不是一个“使这个函数内联”。另一方面,编译器通常会内联函数,而不管是否有inline关键字。它查看函数的大小、调用次数以及代码从内联中获得的大小。

如果将函数移动到“foo.cpp”,它只会在“foo.cpp”编译单元(通常,编译单元=源文件)内内联。

那是除非您有一个能够“整个程序优化”或类似技巧的编译器,并启用该功能 - 这基本上意味着编译器不会生成准备好将目标文件与机器代码链接,而是生成“已解析但不完全翻译成机器指令”目标文件。然后,当最终将可执行文件(或共享库)放在一起时,编译器/链接器将从“中途”代码生成一大块机器代码。MS 和 GCC 都支持这一点,但我不知道它对大型项目的效果如何。

编辑:

根据 Mooing Duck 的评论:一个inline函数不会在目标文件中创建一个真正的函数名,因此链接器也可能会给出错误unresolved symbol int Foo::Method()[或某种程度的措辞]。

结束编辑。

如果性能很关键,您应该测量当前代码的性能,然后进行更改,然后再次测量。如果它显着不同,你会得到你的答案。如果它更快(例如,因为更少的内联导致其他代码位的缓存命中率更高),那很好。如果速度较慢,则必须将(某些)函数放回头文件中。或者忍受它变慢......或者找到其他方法让它再次变得更快......选择是你的(如果你在一个团队中工作,其他人可能会对最终决定有发言权,当然)。任何人都几乎不可能说肯定会走哪条路,至少要了解整个程序架构以及课堂上发生的事情 - 给定名称“foo.cpp”

于 2013-06-11T22:07:47.120 回答
2

这可能会令人困惑,但您不应该想到inline让编译器内联一个函数的目的。(现代编译器在何时应该内联函数方面比你聪明得多)。

不,真正的目的inline是告诉链接器不要担心函数的多个定义。如果将(非成员)函数的定义放在头文件中,则应将其标记inline以避免链接器错误。

于 2013-06-11T21:54:43.963 回答
0

2. How typically the removal of the inline keyword affect the performance (practically all my methods are inlined)?

inline关键字告诉编译器采用该函数的实现代码并将其放在函数调用的位置。这减少了堆栈上的函数调用次数,如果使用正确,可以提高程序的性能。

inline关键字只能用于小函数。Get 和 Set 函数就是很好的例子。他们设置一个变量的值或返回一个变量的值。

如果你用大量代码创建一个函数inline,它会大大增加你的代码大小(取决于函数代码的大小和该函数的使用次数),实际上会降低程序的性能。

于 2013-06-11T21:56:04.803 回答