对会员功能的重复呼叫会受到伤害吗?

时间:2011-02-24 04:33:39

标签: c++ performance function inline member-functions

我已经用Java和C编程了,现在我正试图用C ++搞定。

鉴于此代码:

class Booth {

private :
          int tickets_sold;
public :
          int get_tickets_sold();
          void set_tickets_sold();
};

在Java中,无论我需要tickets_sold的值,我都会反复调用getter。

例如:

if (obj.get_tickets_sold() > 50 && obj.get_tickets_sold() < 75){
     //do something
}

在C中,我只得到结构中特定变量的值:

if( obj_t->tickets_sold > 50 && obj_t->tickets_sold < 75){
    //do something
}

因此,在C语言中使用结构时,我会节省两次我在Java中调用的调用,即两个调用者,我甚至不确定这些是实际调用还是Java以某种方式内联这些调用。

我的观点是,如果我使用与我在C ++中使用Java相同的技术,这两个对getter成员函数的调用是否会花费我,或者编译器会以某种方式知道内联代码? (从而减少了函数调用的开销?)

或者,我最好使用:

int num_tickets = 0;

if ( (num_tickets = obj.get_ticket_sold()) > 50 && num_tickets < 75){
    //do something
}

我想编写严密的代码并避免不必要的函数调用,我会在Java中关心这一点,因为,我们都知道为什么。但是,我希望我的代码具有可读性,并使用privatepublic关键字来正确反映要执行的操作。

7 个答案:

答案 0 :(得分:8)

除非你的程序太慢,否则它并不重要。在99.9999%的代码中,函数调用的开销是微不足道的。写下最清晰,最容易维护,最容易理解的代码,只有在知道性能热点的位置后才开始调整性能,如果你有的话。

也就是说,现代C ++编译器(以及一些链接器)可以并且将内联函数,特别是像这样的简单函数。

答案 1 :(得分:5)

如果你只是在学习这门语言,你真的不应该为此担心。除非另有证明,否则请考虑它。也就是说,这里有很多误导或不完整的答案,所以为了记录,我将充实一些更微妙的含义。考虑你的课程:

class Booth
{
  public:
    int get_tickets_sold();
    void set_tickets_sold();
  private:
    int tickets_sold;
};

尚未指定get和set函数的实现(称为 definition )。如果您在类声明中指定了函数体,那么编译器会认为您已隐式请求它们被内联(但如果它们过大则可能会忽略它)。如果您稍后使用inline关键字指定它们,则具有完全安全的效果。草率...

class Booth
{
  public:
    int get_tickets_sold() { return tickets_sold; }
    ...

...和...

class Booth
{
  public:
    int get_tickets_sold();
    ...
};

inline int Booth::get_tickets_sold() { return tickets_sold; }

...是等价的(至少在标准鼓励我们期望的方面,但是单个编译器启发式可能会有所不同 - 内联是编译器可以自由忽略的请求。)

如果稍后在没有inline关键字的情况下指定函数体,则编译器没有义务内联它们,但仍可能选择这样做。如果它们出现在相同的翻译单元中(即在您正在编译的.cc / .cpp / .c ++ / etc。“实现”文件或直接或间接包含的某些标题中),则更有可能这样做。 如果实现仅在链接时可用,那么函数可能根本不会内联,但这取决于特定编译器和链接器交互和协作的方式。 只是启用优化和期待魔法的问题。为了证明这一点,请考虑以下代码:

// inline.h:
void f();

// inline.cc:
#include <cstdio>
void f() { printf("f()\n"); }

// inline_app.cc:
#include "inline.h"
int main() { f(); }

建立这个:

g++ -O4 -c inline.cc
g++ -O4 -o inline_app inline_app.cc inline.o

调查内联:

$ gdb inline_app 
...
(gdb) break main
Breakpoint 1 at 0x80483f3
(gdb) break f
Breakpoint 2 at 0x8048416
(gdb) run
Starting program: /home/delroton/dev/inline_app 

Breakpoint 1, 0x080483f3 in main ()
(gdb) next
Single stepping until exit from function main, 
which has no line number information.

Breakpoint 2, 0x08048416 in f ()
(gdb) step
Single stepping until exit from function _Z1fv, 
which has no line number information.
f()
0x080483fb in main ()
(gdb) 

注意执行从main()中的0x080483f3到f()中的0x08048416然后返回到main()中的0x080483fb ...明确内联。这说明内联不能仅仅因为函数的实现是微不足道的。

请注意,此示例使用目标文件的静态链接。显然,如果您使用库文件,您可能实际上希望避免专门内联,以便您可以更新库而无需重新编译客户端代码。对于无论如何在加载时隐式完成链接的共享库,它甚至更有用。

通常,提供普通函数的类使用两种形式的expect-inlined函数定义(即内部类或inline关键字),如果可以在任何性能关键循环中调用这些函数,但是反之亦然,通过内联函数,您可以强制重新编译客户端代码(相对较慢,可能没有自动触发)并重新链接(快速,对于共享库在下次执行时发生),而不是仅仅重新链接,以便获取更改到功能实现。

这些考虑因素令人讨厌,但是对这些权衡的有意管理是允许企业使用C和C ++扩展到数万亿行和数千个单独项目的过程,这些项目在几十年内共享各种库。

另一个小细节:作为一个球场图,一个外线获取/设置函数通常比同等内联代码慢一个数量级(10x)。这显然会随CPU,编译器,优化级别,变量类型,缓存命中/未命中等而变化。

答案 2 :(得分:4)

不,重复调用成员函数不会受到伤害。

如果它只是一个getter函数,它几乎肯定会被C ++编译器(至少使用发布/优化版本)内联,并且Java虚拟机可能会&#34;弄清楚&#34;经常调用某个函数并对其进行优化。因此,一般来说,使用函数几乎没有性能损失。

您应该始终首先编写可读性代码。当然,that's not to say that you should completely ignore performance outright,但如果性能不可接受,那么您可以随时分析代码并查看最慢的部分。

此外,通过限制对getter函数后面的tickets_sold变量的访问,您几乎可以保证唯一可以将tickets_sold变量修改为Booth的成员函数的代码。这允许您在程序行为中强制执行不变量。

例如,tickets_sold显然不会是负值。这是结构的不变量。您可以通过将tickets_sold设为私有来强制执行该不变量,并确保您的成员函数不违反该不变量。 Booth类使tickets_sold可用作&#34;只读数据成员&#34;通过getter函数给其他人,并仍然保留不变量。

将其设为公共变量意味着任何人都可以去追踪tickets_sold中的数据,这基本上完全破坏了您在tickets_sold上强制执行任何不变量的能力。这使得某人可以将负数写入tickets_sold,这当然是荒谬的。

答案 3 :(得分:1)

编译器很可能像这样内联函数调用。

答案 4 :(得分:1)

class Booth {
public:
    int get_tickets_sold() const { return tickets_sold; }

private:
    int tickets_sold;
};

您的编译器应该内联get_tickets_sold,如果没有,我会非常惊讶。如果没有,您需要使用新的编译器或启用优化。

答案 5 :(得分:0)

任何有价值的编译器都可以轻松地将getter优化为直接成员访问。唯一不会发生的事情是当您明确禁用优化(例如,对于调试版本)或者您正在使用脑死驱动程序时(在这种情况下,您应该认真考虑放弃它以获得真正的编译器)。

答案 6 :(得分:0)

编译器很可能会为你做这项工作,但总的来说,对于像这样的事情,我会更多地从C角度而不是Java角度处理它,除非你想让成员访问const引用。但是,在处理整数时,在副本上使用const引用通常没什么价值(至少在32位环境中,因为两者都是4个字节),所以你的例子在这里并不是一个好的...也许这可能说明为什么要在C ++中使用getter / setter:

class StringHolder
{
public:
  const std::string& get_string() { return my_string; }
  void set_string(const std::string& val) { if(!val.empty()) { my_string = val; } }
private
  std::string my_string;
}

这会阻止修改,除非通过setter,然后允许您执行额外的逻辑。但是,在这样一个简单的类中,这个模型的值是nil,你只是让那个调用它的编码器输入更多而没有真正添加任何值。对于这样的课程,我不会有getter / setter模型。