在我的实现文件(.cc文件)中,我经常发现在类定义中定义成员函数很方便(比如Pimpl类的函数)。例如:
struct X::Impl {
void DoSomething() {
...
}
};
而不是
struct X::Impl {
void DoSomething();
};
void X::Impl::DoSomething() {
...
}
我认为这比在类定义之外实现函数更好,原因有几个。它增强了可读性,并有助于保持方法的小型化(通过简化添加方法)。由于您不必更新方法声明,因此代码也更易于维护。
我看到的唯一缺点是类声明中定义的方法是隐式内联的,由于目标代码的大小增加,这通常不合适。
我的问题是:
我有这个权利吗?我缺少这种做法还有其他缺点吗?
隐含的内联是否需要担心?编译器是否足够聪明以拒绝我对内联方法的隐式请求,这些方法不应该内联?
是否可以(通过编译器扩展或其他方式)声明类定义中定义的方法不被内联?
答案 0 :(得分:3)
简单的答案是你不应该在意。在类定义中定义的成员函数是隐式inline
,但这并不意味着它们是内联(即代码不需要在调用地点内联)。
编译器实现者已经投入了相当多的时间和资源来提出启发式方法,根据函数的大小,复杂性以及是否可以内联它来确定是否应该进行实际内联。 (递归函数不能内联 [*] )。编译器提供了有关生成的代码及其运行的体系结构的更多信息,而不是我们大多数人所拥有的。相信它,然后如果您认为可能存在问题,配置文件,并且如果分析表明您应该更改代码,请执行此操作,但在之后做出明智的决定。
如果要验证函数是否实际上是内联,您可以查看程序集并检查是否有函数调用或代码是否真的内联。
[*] 如果编译器可以将递归转换为迭代,就像尾递归一样,那么理论上可以将转换后的函数内联。但是,带循环的函数无论如何都有较小的内联概率......
答案 1 :(得分:1)
我不知道防止内联的便携方式,但是使用GCC你可以使用属性。例如:
struct X::Impl {
void DoSomething() __attribute__((noinline)) {
...
}
};
答案 2 :(得分:0)
添加内联选项将保存的所有CPU时间。花费少于你自己的花费。线性路径长度令人担忧的是现在每天处理数万亿次的路径。 L1中有40亿足够接近第二。 250次,不是五分钟。如果没有人抱怨性能,你和你的编译器的选择至少在正确的联赛中发挥作用。
关于可读性的意见差异很大。重要的是你的观众的意见。
答案 3 :(得分:0)
通常,编译器会内联它所需的内容,并且不会过多关注程序员给出的任何inline
关键字。 inline
通常与static
或多或少相同。
例如,递归函数通常不能完全内联(您内联的函数体将再次包含应该内联的调用)。要测试特定编译器的行为,您可以例如定义一个类:
class Fib {
public:
int calc(int i);
};
int Fib::calc(int i) {
if (i > 1)
return calc(i-1) + calc(i-2);
return 1;
}
根据这个定义,如果编译器感觉有义务内联对calc
的所有调用,那么在合理的时间或大小下,如下所示的示例程序将无法编译:
#include "tst.hpp"
#include <iostream>
int main() {
Fib f;
std::cout << f.calc(1000) << std::endl;
};
因此,您可以通过编译此程序来测试编译器的行为。如果编译成功,则编译器不会内联对Fib::calc
的所有调用。