将头文件中的内联方法移动到.cpp文件中

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 关键字会如何影响性能(我的大部分方法都是内联的)?
非常感谢您的帮助。

2
inline 只是表示你想要将函数内联,最终决定由编译器做出,它会决定你的函数是否被内联。 - user1182183
3
它很可能会将其嵌入某些位置而不是其他位置,或者根本不嵌入,或者在全部位置进行嵌入,或者在运行时更改,等等。 - Peter Wood
6个回答

16
如果您将函数定义从头文件移动到cpp文件中,您必须删除该函数所有位置上的inline关键字。使用旧链接器可能会稍微降低性能,但使用现代链接器,您应该不会注意到实际性能方面的区别。
有一些情况下公共成员函数可以是内联的,但这只是一个不好的想法,请不要这样做。对于某些私有成员函数标记为内联可能有道理,但实际上您真正想要的是__attribute__((always_inline))或__forceinline。
在极少数情况下,它会有所影响,但99%的时间不会,而剩余的99.9%您也不在意。如果测量结果表明您达到了万分之一的情况,您可以使用上述的__forceinline。

1
然而,如果标记为inline的函数仅从同一翻译单元内调用,则仍可以在cpp中指定inline。但通常不适用于公共成员函数。如果仅供类使用,则可能会将其设置为私有。无论如何,编译器(或某些链接器)可能会决定内联此类函数,如果它认为值得这样做。 - Pete
@Pete:没错,但是我认为即使在某些情况下它能够工作,将其内联到cpp中也是一个非常糟糕的想法。 - Mooing Duck
你肯定不会为公共成员函数这样做。但是,我曾经因为性能分析而使用过 __forceinline 来内联一个私有成员函数。 - Pete
@Pete:__forceinlineinline是不同的东西,但我会提一下。 - Mooing Duck
@MooingDuck非常感谢您的回答。我会注意到您的建议。+1并接受。 - Vitality

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 的意思是“我想在头文件中定义这个函数”。从这个角度看,当你将函数移动到 cpp 文件中时,应该清除 inline。有趣的是,有几个地方会隐式地使函数成为内联函数,其中之一是类成员函数。
struct Foo {
    void bar() { /* do something */ }
};

我看到有人将函数标记为inline,但这是完全多余的。编译器会自动这样做;不需要担心ODR,也没有性能可提高。

另一个地方是在模板中。由于模板必须在头文件中定义,它们免于ODR,因此将其标记为inline是多余的。


5

类中的关键字inline是多余的,如果你有一个函数体,它是默认存在的。

在实现文件中也相当冗余。

唯一使用它的情况是,如果你在头文件中定义了一个自由函数(或在头文件中定义了一个成员函数,但在类外部),以避免多个函数体。

从优化方面来看,在大多数现代编译器上,它甚至更加冗余,它们会毫不犹豫地内联任何东西,或者随意忽略你的关键字。

inline的使用必须是一致的!根据7.1.2p4:

内联函数应该在每个使用它的翻译单元中进行定义,并且在所有情况下都具有完全相同的定义(3.2)。[注:在翻译单元中出现内联函数的调用可能先于其定义。—end note] 如果在翻译单元中的第一个声明之前出现函数的定义为内联,则程序是非法的。如果具有外部链接的函数在一个翻译单元中声明为内联,则它在出现的所有翻译单元中都必须被声明为内联;不需要进行诊断。...


4
如果函数不是微小的(或者需要多个参数,但不做太多事情,例如构造函数等,它需要一堆东西,并将其只是复制到类内部的某个地方),那么将其内联化将对性能几乎没有影响。设置器和获取器通常是内联的好候选对象,因为它们(通常)只是从一个地方复制数据到另一个地方,并且可以在调用发生的地方轻松完成。
正如其他人所说,“请编译器,如果我可以请求您,请考虑内联这个函数”——这不是“使此函数内联”。另一方面,编译器通常会内联函数,无论是否有内联关键字。它查看函数的大小、调用次数以及通过内联变得更大的代码量。
如果将函数移动到“foo.cpp”,则它仅在“foo.cpp”编译单元(通常,编译单元=源文件)中得到内联。
除非您拥有能够进行“整个程序优化”或类似技巧的编译器并启用该功能,否则将这意味着编译器不会生成具有机器代码的可链接成的对象文件,而是生成了一个“解析但未完全转换为机器指令”的对象文件。然后,当最终组合可执行文件(或共享库)时,编译器/链接器将从“中途”代码生成一个大的机器代码块。MS和GCC都支持此功能,但是我不知道它对大型项目的效果如何。
编辑:
根据 Mooing Duck 的评论:内联函数在对象文件中不会产生真正的函数名称,因此链接器也可能会出现“未解决的符号int Foo:: Method()”等错误信息。
结束编辑。
如果性能至关重要,则应先测量当前代码的性能,然后进行更改,并再次测量。如果有显著差异,您将得到答案。如果它更快(例如由于较少的内联导致其他代码段的缓存命中率更高),那么很好。如果它变慢了,您就必须把(某些)函数放回头文件中。或者接受它变慢的事实...或找到其他使它再次变快的方法...选择权在你手中(当然,如果你在团队中工作,其他人也可能对最终决策有发言权)。任何人都几乎不可能确切地说出它会走哪条路,而不至少了解整个程序架构以及类中发生的事情 - 鉴于帖子中的“foo.cpp”名称,这可能不是真正的代码...

1
移动“inline”函数定义到cpp文件可能会出现链接器错误,你没有考虑到吗? - Mooing Duck

2
可能有些令人困惑,但是你不应该认为 inline 的作用是让编译器内联一个函数。(现代编译器在确定函数是否应该内联方面比你聪明多了)。

实际上,inline 的真正目的是告诉链接器不要担心函数的多重定义问题。如果你把(非成员)函数的定义放在头文件中,你应该使用 inline 标记来避免链接器错误。

这完全没有回答问题。 - Mooing Duck

0

2. 如果我移除了 inline 关键字,会对性能产生什么影响(我的大部分方法都是内联的)?

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

inline 关键字应仅用于小型函数。获取和设置函数是很好的例子。它们设置一个变量的值或返回一个变量的值。

如果您将具有大量代码的函数标记为 inline,它可能会大大增加代码的大小(取决于函数代码的大小以及该函数被使用的次数),并实际上降低程序的性能。


这根本没有试图回答问题。 - Mooing Duck
不确定为什么它没有(尝试)回答他的第二个问题。我只是说添加/删除内联关键字会根据他将函数实现为内联的方式增加/减少程序的性能。由于他没有展示函数的实现,所以我的回答是一般性的。再次说明,我是新来的,如果我的回答没有有意义的贡献,我深表歉意。 - Doug
inline”关键字应该被移除吗? - Mooing Duck
重新阅读问题和你的回答后,我意识到了我的错误。我以为他的两个问题是互相排斥的。你是正确的,感谢你帮助我理解这个问题/答案。如果可以的话,我会给你点赞。 - Doug

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