12

Is there a need performance-wise for inline functions to pass its arguments by const reference like

foo(const T & a, const T &b)

compared to by value

foo(T a, T b)

if I don't change the values of a and b in the function? Does C++11 change recommend anything specific here?

Nordlöw
  • 11,838
  • 10
  • 52
  • 99
  • 2
    A value is a value and a reference is a reference. Passing a const reference when meaning instead passing a value is a mistake that can bite back badly. See http://stackoverflow.com/questions/4705593/int-vs-const-int/4705871#4705871 – 6502 Oct 02 '11 at 16:59
  • @6502: That's a great example of a case where using a reference makes the logic more complicated (`v.push_back(v[0])` is legal, because the standard library must include that extra logic). – Ben Voigt Oct 02 '11 at 17:06
  • @BenVoigt: Nice to see this request has been added to the standard. Where is it stated? – 6502 Oct 02 '11 at 17:15
  • @6502: If the reference is valid when `push_back` is called, it's the responsibility of the standard library to do the right thing with it. – Ben Voigt Oct 02 '11 at 18:33
  • @BenVoigt: Sorry but I don't agree. When calling a function passing a reference if the referenced object doesn't live long enough for the duration of the function it's a caller problem (the callee has no control on it). AFAIK the standard doesn't say that the copy operation must be done before the deallocation: if an implementation wants to do so ok, but a valid C++ compiler can make daemons fly off my nose still remaining compliant. By the way things are even more complex now with C++0x... can reallocation use move constructor? If so just delaying destruction of old storage is not enough... – 6502 Oct 02 '11 at 20:14
  • @6502: This is something that the library implementer has control over and the caller doesn't. The caller is responsible to not delete the passed object during the function (for example from a callback or another thread), and the callee is responsible to not delete the passed object until it's no longer needed. – Ben Voigt Oct 02 '11 at 20:55
  • Anyway, it's already been proved that `v.push_back(v[0])` is safe: http://stackoverflow.com/questions/6210688/what-is-the-right-way-to-avoid-aliasing-e-g-when-adding-an-element-of-a-conta/6212163#6212163 – Ben Voigt Oct 02 '11 at 21:04
  • @BenVoigt: Also after discussing this in the chat Alf P. Steinbach brought another important point (http://chat.stackoverflow.com/transcript/message/1594676#1594676). If you pass a function a reference to an object that doesn't live long enough then you're already in UB land and the C++ implementation is not required to behave in any prescribed way. `v[0]` is not going to live long enough in case of reallocation and therefore the implementation is free to do whatever it wants to do. – 6502 Oct 03 '11 at 10:38
  • @6502: But you're passing a reference which IS valid. So you're not in UB-land. If the caller caused the reference to become invalid, that'd be on the caller. But if the standard library invalidates a reference, it needs to not use the reference afterwards. – Ben Voigt Oct 03 '11 at 15:05
  • @BenVoigt: I'm in UB land because I'm passing a reference to an object that will not live long enough because it's valid only until there is a reallocation. So it's my fault (the caller) to pass it to a function that may need to do a reallocation, possibly before using it. In general the called function has no way to control how long will live an object received by reference, so it's a caller responsibility to ensure that it will live long enough. Imagine that I pass `push_back` a reference to an object that is destroyed when `::operator new` is called... would still be a library fault? – 6502 Oct 03 '11 at 16:03
  • @6502: But there's clearly a difference between an object destroyed by `push_back` itself, and one destroyed by code that only the caller has control over. – Ben Voigt Oct 03 '11 at 17:15
  • (Postscript to above comments -- I took 6502's side [in another discussion](http://stackoverflow.com/q/18788780/103167) and no one agreed) – Ben Voigt Jan 24 '14 at 16:39

3 Answers3

13

Pass by value can only elide the copy-constructor call if the argument is a temporary.

Passing primitive types by const reference will have no cost when the function is inlined. But passing a complex lvalue by value will impose a potentially expensive copy constructor call. So prefer to pass by const reference (if aliasing is not a problem).

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • In addition to aliasing problems there are lifetime problems. – 6502 Oct 02 '11 at 17:05
  • @6502: On return values, definitely. For parameters, lifetime issues can occur, but they are very rare. And I would consider them a subset of aliasing issues anyway. – Ben Voigt Oct 02 '11 at 17:08
  • @BenVoigt `Passing primitive types by const reference will have no cost when the function is inlined.` Do you mean, "will have no benefit in term of cost"? Can you confirm if there's anything to gain passing a `const int&` or a `const int` when the function is inlined? – Antonio Jan 24 '14 at 15:52
  • @Antonio: No, I meant what I said. In general there are significant costs to passing by `const` reference: the callee has to access a pointer in addition to the actual data, which means twice the memory accesses. The callee also has to worry about aliasing, which inhibits several optimizations. When the function is inlined, the compiler can determine whether aliasing is actually possible and often can do those optimizations and access the data directly instead of through an extra pointer. – Ben Voigt Jan 24 '14 at 16:23
4

Theoretically the ones without reference might be copied in memory as there is the possibility that your inline function might be modifying them (even if it actually doesn't).

In many cases the compiler is smart enough to pick out that kind of thing but it will depend on the compiler and the optimization settings. Also if your function call on any non-const member functions in the class variables then your compiler will have to be smart enough to check if they are modifying anything as well.

By using a const reference you can basically give it a fairly clear indication.

EDIT: I just to a look at the machine code for a simple test program compiled with GCC 4.6 in ddd. The generated code seemed identical so it does seem to be optimized out. It's still good practice though for other compilers and if nothing else give a clear indication of the code's intent. It's also possible there are more complex situations that the compiler can't optimize though.

Also the llvm online dissembler demo shows identical bitcode is generated there too. If you turn off optimization it is slightly longer without the const reference.
* 1964 bytes - No const reference (and no other consts on functions/paramaters)
* 1960 bytes - Just no const reference but other consts.
* 1856 bytes - With consts and const reference.

David C. Bishop
  • 6,437
  • 3
  • 28
  • 22
  • 1
    If the inline function passes a and b to another function, it may not be possible to optimize out the copy. For example, if the inline function calls non-inline function `bar(a)` the compiler must copy because bar might do `if (&a == &special) ...` and the original caller may have passed `special`. – Raymond Chen Oct 02 '11 at 16:46
  • Try again, with a non-POD type. – Ben Voigt Oct 02 '11 at 16:49
  • It doesn't matter for the optimizer if a member function is declared `const` or not. Const-correctness is invisible for the optimizer and is something that has been designed only to help programmers (by giving compile-time errors if you violate the const declarations), not the optimizer. – 6502 Oct 02 '11 at 16:51
  • @6502: const-correctness does affect optimization in some cases, although I agree than constness of a member function is not one of them. – Ben Voigt Oct 02 '11 at 16:58
  • @Ben Voigt: What can affect optimization are const **values**. Constness of a reference (as in `const X &`) or of a pointer (as in `const X *`) is never an help for the optimizer because it's a declaration that says nothing about the const-ness of the object being referenced or pointed to (those declaration only determine what operations are valid on the pointer/reference). – 6502 Oct 02 '11 at 17:02
1

Pass by reference is faster than by value depending on data type.
However for inline functions the function body (and thus all references / passed in values) are added to the line of code they are used in anyway so technically there are no variables being passed around, only more lines of code in the same area.

reference http://www.cprogramming.com/tutorial/lesson13.html

There is also a very helpful answer under the question should-i-take-arguments-to-inline-functions-by-reference-or-value

  • Example may have been misleading, removed -
Community
  • 1
  • 1
Serdalis
  • 10,296
  • 2
  • 38
  • 58
  • 1
    This is entirely speculation. – pmr Oct 02 '11 at 16:50
  • 1
    Passing by reference is faster than by value **depending on the type**. Also code that uses a reference has additional requirements (for correctly handling aliasing) and may require an extra indirection not needed for native types passed by value. – 6502 Oct 02 '11 at 16:57
  • Apologies, I got pass by pointer mixed with value for some strange reason, you are correct it is only faster for some data types. I also point you http://stackoverflow.com/questions/722257/should-i-take-arguments-to-inline-functions-by-reference-or-value which supports my point – Serdalis Oct 02 '11 at 17:17