2012-06-01 32 views
2

我在执行文件(.cc的文件),我经常发现它的方便类定义来定义的成员函数(如平普尔类的功能) 。例如:C++ - 避免在类定义中定义的函数的隐式内联

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

,而不是

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

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

我觉得这是最好实现功能的类定义之外的几个原因。它增强了可读性,并促进了保持方法小的做法(通过使它们易于添加)。代码也更容易维护,因为您永远不必更新方法声明。

我看到的唯一的缺点是在类声明中定义的方法是隐式内联,这是因为在对象代码的尺寸的增加通常不期望的。

我的问题是:

  1. 我有这个权利?这种做法还有其他缺点,我错过了吗?

  2. 隐式内联是否担心什么?编译器是否足够聪明以拒绝我的隐式请求来内联不应该内联的方法?

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

+4

这不是更具可读性,功能应该小的想法来自教授,而不是现实世界。这是一个虚假的规则。 –

+1

GCC有'-fno-inline',但我认为关闭它会很愚蠢,我相信gcc会做出比我更聪明的决定。 – Flexo

+0

@JohnDibling:同意。值得注意的是,为了可读性的目的,函数*的C++代码应该很小,但生成的代码不需要。让编译器做最好的事情。 –

回答

3

简单的答案是,你不应该在意。在类定义中定义的成员函数隐含地为inline,但这并不意味着它们是内联(即,代码不需要在呼叫位置内联)。

编译器实现者花费了相当多的时间和资源来提出启发式方法,根据函数的大小,复杂性以及是否可以内联来确定是否应该完成实际内联,或者不(递归函数不能内联[*])。编译器比我们大多数人拥有更多关于生成的代码和它将运行的体系结构的信息。相信它,那么如果您觉得可能存在问题,配置文件,并且如果配置文件显示您应该更改代码,请执行此操作,但在事实之后做出明智决定

如果您想验证功能是否实际上是内联与否,你可以看看组装,检查是否有到函数或代码调用真的内联。


[*]如果编译器可以将递归入迭代中,如在尾递归的情况下,则该变换函数可以理论上被内联。但是,具有循环的函数无论如何都具有较低的内联概率。

+0

确切地说,你通常也可以指定递归深度,boost :: xpressive大量使用了这个例子。实际上,我通常在一个.h文件中编写新类,当我发现该类很有用时,我会创建一个伴随的.cpp文件。 – demorge

+0

虽然你不应该关心一个函数是否内联,但是你应该关心一个头文件中的函数定义是否有'inline'关键字,因为如果你在多个翻译中包含头文件,它将防止多重定义链接器错误单元。 'inline'关键字说:“嘿,连接器!这个函数定义可能包含在2,3或42个翻译单元中,确保你只用一个定义。” –

1

我不知道防止内联的便携式方式,但使用GCC可以使用属性。例如:

struct X::Impl { 
    void DoSomething() __attribute__((noinline)) { 
     ... 
    } 
}; 
+0

谢谢。这对我的第三个问题是一个很好的答案。 –

0

将内联选项永远保存的所有CPU时间相加。花费比你自己少。线性路径长度的担忧是每天处理数万亿次执行的路径。 L1的一秒就有四十亿分之一的距离。不是五分钟的250倍。如果没有人抱怨表现,那么你和你的编译器的选择至少在正确的联盟中打球。

关于可读性的意见差别很大。这是你观众的意见。

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

相关问题