2011-02-24 49 views
1

我已经用Java和C编写了程序,现在我正试图用C++让我的手变得肮脏。对成员函数的重复调用是否受到伤害?

鉴于此代码:

class Booth { 

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

在Java中,只要我需要的tickets_sold的价值,我会反复调用,吸气。

例如:

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

在CI将刚刚得到的特定变量在结构中的值:

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

因此,虽然使用C结构,我节省了两个电话是否则我会用Java来做这两个getter,我甚至不确定这些是实际调用还是Java以某种方式内联这些调用。

我的观点是,如果我使用了我在C++中使用的Java技术,那么这两个对getter成员函数的调用会花费我,还是编译器会知道内联代码? (?从而减少函数调用的开销共)

或者,是我最好使用:

int num_tickets = 0; 

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

我想写紧凑的代码,避免不必要的函数调用,我会在乎这在Java中,因为,我们都知道为什么。但是,我希望我的代码可读,并使用privatepublic关键字来正确反映要完成的操作。

+0

为什么你要担心的表现? – GManNickG 2011-02-24 04:59:17

+0

因为我必须做我的分配和运行速度最快可能会获得积分上你的老师 – 2011-02-24 06:14:53

+3

耻辱让你们认为性能问题,而不是好的设计的代码。 – GManNickG 2011-02-24 06:48:20

回答

5

如果你只是学习语言,你真的不应该担心这一点。考虑到速度,直到证明相反。也就是说,这里有很多误导性或不完整的答案,因此为了记录,我将充实一些微妙的含义。考虑你的类:

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

实现(被称为定义)的获取和设置功能尚未指定的。如果你在类声明中指定了函数体,那么编译器会认为你隐式地要求它们被内联(但如果它们过大,可能会忽略)。如果您稍后使用关键字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) 

通知执行从0x080483f3就在main()到0x08048416在f()然后返回到main()中的0x080483fb ...清除而不是内联。这表明内联不能仅仅因为函数的实现是微不足道的。

注意,这个例子是目标文件的静态链接。很显然,如果你使用的库文件,你可以真的想避免内联的功能明确,这样就可以无需重新编译客户端代码更新库。无论如何,在加载时隐式地完成链接的共享库更加有用。

很多时候,如果这些函数可以在任何性能关键的循环中被调用,那么提供微不足道的函数的类会使用两种形式的预期内联函数定义(即在类中或关键字inline)是通过内联函数,你强制客户端代码重新编译(相对缓慢,可能没有自动触发)和重新链接(快速,共享库发生在一次执行),而不仅仅是重新链接,以捡更改功能实现。

这些考虑很烦人,但是这些权衡的谨慎管理是允许企业使用C和C++来扩展到数十亿和数亿行以及数千个单个项目,并且几十年来共享各种图书馆。另一个小细节:作为一个球形图,一个非线性get/set函数通常比等效内联代码慢大约一个数量级(10倍)。这显然与CPU,编译器优化级别,变量类型,缓存命中/缺失等变化..

+0

谢谢.. !!!这是非常有见地的,你现在已经明确地告诉我了! – 2011-02-24 06:24:20

+0

@Rahul:你很受欢迎。希望你喜欢C++ - 这是一门伟大的语言。干杯。 – 2011-02-24 06:49:30

4

不,对成员函数的重复调用不会造成伤害。

如果它只是一个getter函数,它几乎肯定会被C++编译器内联(至少在发布/优化版本中),Java虚拟机可能会“弄清楚”某个函数被频繁调用并优化为了那个原因。所以在一般情况下使用函数几乎没有性能损失。

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

此外,通过限制访问tickets_sold变量背后getter函数,你几乎可以保证的是,可以修改tickets_sold变量的Booth成员函数的唯一代码。这使您可以在程序行为中实施不变量。例如,tickets_sold显然不会是一个负值。这是一个不变的结构。您可以通过将tickets_sold私人化并确保您的成员函数不违反该不变量来强制执行该不变。 Booth类使得tickets_sold可通过getter函数作为“只读数据成员”提供给其他人,并仍保留不变量。

使其成为公共变量意味着任何人都可以去践踏tickets_sold中的数据,这些数据基本上完全破坏了您在tickets_sold上强制执行任何不变量的能力。这使得有人能够将负数写入tickets_sold,这当然是无意义的。

1

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

8

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

这就是说,现代C++编译器(和一些连接),可以和将内联函数,尤其是简单的功能,如这一个。

+1

+1它们不仅可以,而且它们不仅可以,而且它们也可以根据自己的判断选择不内联函数,已经把'inline'标签放在了!它认为这只是一个建议。 – corsiKa 2011-02-24 05:19:34

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

private: 
    int tickets_sold; 
}; 

你的编译器应该直列get_tickets_sold,我会,如果它没有很惊讶。如果不是,您需要使用新的编译器或打开优化。

0

任何编译器值得其盐将容易优化吸气剂为直接成员访问。唯一不会发生的情况是,当你明确禁用了优化(例如对于调试版本)或者如果你正在使用一个大脑死亡的编译器(在这种情况下,你应该认真考虑将它切换为一个真正的编译器)。

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然后这将允许执行额外的逻辑的修改。然而,在这样一个简单的类中,这个模型的价值是零,你只是让编码者调用它的类型更多,并没有真正添加任何值。对于这样的班级,我不会有getter/setter模型。