C ++ - 避免在类定义中定义的函数的隐式内联

时间:2012-06-01 23:15:23

标签: c++

在我的实现文件(.cc文件)中,我经常发现在类定义中定义成员函数很方便(比如Pimpl类的函数)。例如:

struct X::Impl {
    void DoSomething() {
        ...
    }
};

而不是

struct X::Impl {
    void DoSomething();
};

void X::Impl::DoSomething() {
    ...
}

我认为这比在类定义之外实现函数更好,原因有几个。它增强了可读性,并有助于保持方法的小型化(通过简化添加方法)。由于您不必更新方法声明,因此代码也更易于维护。

我看到的唯一缺点是类声明中定义的方法是隐式内联的,由于目标代码的大小增加,这通常不合适。

我的问题是:

  1. 我有这个权利吗?我缺少这种做法还有其他缺点吗?

  2. 隐含的内联是否需要担心?编译器是否足够聪明以拒绝我对内联方法的隐式请求,这些方法不应该内联?

  3. 是否可以(通过编译器扩展或其他方式)声明类定义中定义的方法不被内联?

4 个答案:

答案 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的所有调用。