重复调用成员函数会造成伤害吗?
我已经用 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
}
在 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中关心这一点,因为我们都知道为什么。但是,我希望我的代码具有可读性,并使用 private
和 public
关键字来正确反映要执行的操作。
I have programmed in both Java and C, and now I am trying to get my hands dirty with C++.
Given this code:
class Booth {
private :
int tickets_sold;
public :
int get_tickets_sold();
void set_tickets_sold();
};
In Java, wherever I needed the value of tickets_sold
, I would call the getter repeatedly.
For example:
if (obj.get_tickets_sold() > 50 && obj.get_tickets_sold() < 75){
//do something
}
In C I would just get the value of the particular variable in the structure:
if( obj_t->tickets_sold > 50 && obj_t->tickets_sold < 75){
//do something
}
So while using structures in C, I save on the two calls that I would otherwise make in Java, the two getters that is, I am not even sure if those are actual calls or Java somehow inlines those calls.
My point is if I use the same technique that I used in Java in C++ as well, will those two calls to getter member functions cost me, or will the compiler somehow know to inline the code? (thus reducing the overhead of function call altogether?)
Alternatively, am I better off using:
int num_tickets = 0;
if ( (num_tickets = obj.get_ticket_sold()) > 50 && num_tickets < 75){
//do something
}
I want to write tight code and avoid unnecessary function calls, I would care about this in Java, because, well, we all know why. But, I want my code to be readable and to use the private
and public
keywords to correctly reflect what is to be done.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
除非你的程序太慢,否则这并不重要。在 99.9999% 的代码中,函数调用的开销是微不足道的。尽可能编写最清晰、最容易维护、最容易理解的代码,并且只有在了解性能热点(如果有的话)后才开始调整性能。
也就是说,现代 C++ 编译器(和一些链接器)可以并且将会内联函数,尤其是像这样的简单函数。
Unless your program is too slow, it doesn't really matter. In 99.9999% of code, the overhead of a function call is insignificant. Write the clearest, easiest to maintain, easiest to understand code that you can and only start tweaking for performance after you know where your performance hot spots are, if you have any at all.
That said, modern C++ compilers (and some linkers) can and will inline functions, especially simple functions like this one.
如果你只是学习语言,你真的不应该担心这个。考虑得足够快,直到证明并非如此。也就是说,这里有很多误导性或不完整的答案,因此为了记录在案,我将充实一些更微妙的含义。考虑您的类:
get 和 set 函数的实现(称为定义)尚未指定。如果您在类声明中指定了函数体,那么编译器会认为您已隐式请求将它们内联(但如果它们太大,则可能会忽略这一点)。如果您稍后使用
inline
关键字指定它们,则具有完全安全的效果。总之......和...
...是等效的(至少在标准鼓励我们期望的方面,但各个编译器启发式可能会有所不同 - 内联是编译器可以自由忽略的请求)。
如果稍后在没有
inline
关键字的情况下指定函数体,则编译器没有义务内联它们,但仍可以选择这样做。如果它们出现在同一翻译单元中(即在您正在编译的 .cc/.cpp/.c++/etc.“实现”文件中或直接或间接包含在其中的某些标头中),则更有可能这样做。 如果实现仅在链接时可用,则函数可能根本无法内联,但这取决于特定编译器和链接器交互和协作的方式。这不仅仅是简单的启用优化和期待魔法的问题。为了证明这一点,请考虑以下代码:构建此:
调查内联:
注意执行从 main() 中的 0x080483f3 到 f() 中的 0x08048416,然后回到 main() 中的 0x080483fb...显然不是 内联。这说明不能仅仅因为函数的实现微不足道就期望内联。
请注意,此示例使用目标文件的静态链接。显然,如果您使用库文件,您可能实际上希望避免内联专门的函数,以便您可以更新库而无需重新编译客户端代码。它对于共享库更有用,因为无论如何链接都是在加载时隐式完成的。
通常,提供简单函数的类会使用两种形式的预期内联函数定义(即在类内部或使用
inline
关键字),如果这些函数预计会在任何性能关键循环内调用,但是相反的考虑因素是,通过内联函数,您强制客户端代码重新编译(相对较慢,可能没有自动触发)和重新链接(快速,因为共享库在下次执行时发生),而不是仅仅重新链接,以便获取更改到函数的实现。这些考虑因素很烦人,但对这些权衡的刻意管理使得企业对 C 和 C++ 的使用能够扩展到数千万行和数亿行以及数千个单独的项目,所有这些都在几十年内共享各种库。
另一个小细节:作为一个大概数字,外线 get/set 函数通常比等效的内联代码慢一个数量级 (10 倍)。这显然会因 CPU、编译器、优化级别、变量类型、缓存命中/未命中等而有所不同。
If you're just learning the language, you really shouldn't worry about this. Consider it fast enough until proven otherwise. That said, there are a lot of misleading or incomplete answers here, so for the record I'll flesh out a few of the subtler implications. Consider your class:
The implementation (known as a definition) of the get and set functions is not yet specified. If you'd specified function bodies inside the class declaration then the compiler would consider you to have implicitly requested they be inlined (but may ignore that if they're excessively large). If you specify them later using the
inline
keyword, that has exactly the safe effect. Summarily......and...
...are equivalent (at least in terms of what the Standard encourages us to expect, but individual compiler heuristics may vary - inlining is a request that the compiler's free to ignore).
If the function bodies are specified later without the
inline
keyword, then the compiler is under no obligation to inline them, but may still choose to do so. It's much more likely to do so if they appear in the same translation unit (i.e. in the .cc/.cpp/.c++/etc. "implementation" file you're compiling or some header directly or indirectly included by it). If the implementation is only available at link time then the functions may not be inlined at all, but it depends on the way your particular compiler and linker interact and cooperate. It is not simply a matter of enabling optimisation and expecting magic. To prove this, consider the following code:Building this:
Investigating the inlining:
Notice the execution went from 0x080483f3 in main() to 0x08048416 in f() then back to 0x080483fb in main()... clearly not inlined. This illustrates that inlining can't be expected just because a function's implementation is trivial.
Notice that this example is with static linking of object files. Clearly, if you use library files you may actually want to avoid inlining of the functions specifically so that you can update the library without having to recompile the client code. It's even more useful for shared libraries where the linking is done implicitly at load time anyway.
Very often, classes providing trivial functions use the two forms of expected-inlined function definitions (i.e. inside class or with
inline
keyword) if those functions can be expected to be called inside any performance-critical loops, but the countering consideration is that by inlining a function you force client code to be recompiled (relatively slow, possibly no automated trigger) and relinked (fast, for shared libraries happens on next execution), rather than just relinked, in order to pick up changes to the function implementation.These kind of considerations are annoying, but deliberate management of these tradeoffs is what allows enterprise use of C and C++ to scale to tens and hundreds of millions of lines and thousands of individual projects, all sharing various libraries over decades.
One other small detail: as a ballpark figure, an out-of-line get/set function is typically about an order of magnitude (10x) slower than the equivalent inlined code. That will obviously vary with CPU, compiler, optimisation level, variable type, cache hits/misses etc..
不,重复调用成员函数不会有什么坏处。
如果它只是一个 getter 函数,它几乎肯定会被 C++ 编译器内联(至少在发布/优化版本中),并且 Java 虚拟机可能会“发现”某个函数被频繁调用并对此进行优化。因此,一般情况下使用函数几乎不会造成性能损失。
您应该始终首先考虑代码的可读性。当然,这并不是说您应该完全忽略性能,但是如果性能如果不可接受,那么您可以随时分析您的代码并查看最慢的部分在哪里。
此外,通过限制对 getter 函数后面的
tickets_sold
变量的访问,您几乎可以保证唯一可以将tickets_sold
变量修改为Booth 的成员函数的代码
。这允许您在程序行为中强制执行不变量。例如,
tickets_sold
显然不会是负值。这是结构的不变量。您可以通过将tickets_sold
设为私有并确保您的成员函数不违反该不变量来强制执行该不变量。Booth
类通过 getter 函数将tickets_sold
作为“只读数据成员”提供给其他所有人,并且仍然保留不变量。将其设为公共变量意味着任何人都可以践踏
tickets_sold
中的数据,这基本上完全破坏了您在tickets_sold
上强制执行任何不变量的能力。这使得有人可以在tickets_sold
中写入负数,这当然是荒谬的。No, repetitive calls to member functions will not hurt.
If it's just a getter function, it will almost certainly be inlined by the C++ compiler (at least with release/optimized builds) and the Java Virtual Machine may "figure out" that a certain function is being called frequently and optimize for that. So there's pretty much no performance penalty for using functions in general.
You should always code for readability first. Of course, that's not to say that you should completely ignore performance outright, but if performance is unacceptable then you can always profile your code and see where the slowest parts are.
Also, by restricting access to the
tickets_sold
variable behind getter functions, you can pretty much guarantee that the only code that can modify thetickets_sold
variable to member functions ofBooth
. This allows you to enforce invariants in program behavior.For example,
tickets_sold
is obviously not going to be a negative value. That is an invariant of the structure. You can enforce that invariant by makingtickets_sold
private and making sure your member functions do not violate that invariant. TheBooth
class makestickets_sold
available as a "read-only data member" via a getter function to everyone else and still preserves the invariant.Making it a public variable means that anybody can go and trample over the data in
tickets_sold
, which basically completely destroys your ability to enforce any invariants ontickets_sold
. Which makes it possible for someone to write a negative number intotickets_sold
, which is of course nonsensical.编译器很可能会像这样内联函数调用。
The compiler is very likely to inline function calls like this.
您的编译器应该内联
get_tickets_sold
,如果没有,我会感到非常惊讶。如果没有,您要么需要使用新的编译器,要么打开优化。Your compiler should inline
get_tickets_sold
, I would be very surprised if it didn't. If not, you either need to use a new compiler or turn on optimizations.任何有价值的编译器都会轻松地将 getter 优化为直接成员访问。唯一不会发生这种情况的是当您显式禁用优化时(例如,对于调试构建)或者您使用的是脑死亡编译器(在这种情况下,您应该认真考虑放弃它而使用真正的编译器)。
Any compiler worth its salt will easily optimize the getters into direct member access. The only times that won't happen are when you have optimization explicitly disabled (e.g. for a debug build) or if you're using a brain-dead compiler (in which case, you should seriously consider ditching it for a real compiler).
编译器很可能会为您完成这项工作,但一般来说,对于这样的事情,我会更多地从 C 角度而不是 Java 角度来处理它,除非您想让成员访问 const 引用。然而,在处理整数时,在副本上使用 const 引用通常没有什么价值(至少在 32 位环境中,因为两者都是 4 字节),所以你的例子在这里并不是一个很好的例子......也许这可能说明为什么要在 C++ 中使用 getter/setter:
这可以防止修改,除非通过 setter,然后允许您执行额外的逻辑。然而,在像这样的简单类中,该模型的值为零,您只是让调用它的编码器输入更多内容,并没有真正添加任何值。对于这样的类,我不会有 getter/setter 模型。
The compiler will very likely do the work for you, but in general, for things like this I would approach it more from the C perspective rather than the Java perspective unless you want to make the member access a const reference. However, when dealing with integers, there's usually little value in using a const reference over a copy (at least in 32 bit environments since both are 4 bytes), so your example isn't really a good one here... Perhaps this may illustrate why you would use a getter/setter in C++:
That prevents modification except through the setter which would then allow you to perform extra logic. However, in a simple class such as this, the value of this model is nil, you've just made the coder who is calling it type more and haven't really added any value. For such a class, I wouldn't have a getter/setter model.