7

I am currently learning C++ from the C++ Primer 5th edition. The book's chapter on functions states that only large objects (large being relative as standard library strings count, but "primitive types" such as int or char don't) should be passed as references.

For instance, the following function serves as an example in the book on when to use reference parameters:

string::size_type find_char(const string &s, char c, string::size_type &occurs){
    /* code to return the first occurrence of c in s, occurs contains total number of occurrences */
}

One of the exercises asks what the rationale for each of the parameter being what it is beyond it's base type. I understand why the string is a reference (to prevent copying of to improve performance) and why it is const (as it doesn't need to be modified). I also understand why the occurs parameter is a reference (as the original variable's value needs to be changed).

What I don't understand is why c isn't a (const) reference. I know that it contains a very small object, but, wouldn't the prevention of any object from being copied, no matter how small, still be a performance improvement?

If there was some downside to using references, then it would make sense. But, I have no idea what such a downside would be. Also, I know that a pointer's address would be copied, but, since references can't be changed I imagine that the called function could access the original variable in exactly the same way that the caller does (assuming that the variable is declared in the caller).

Why should a non-reference parameter ever be used, especially in instances like the above example, unless the parameter needs to be modified for the function's implementation without returning the changed value?

NOTE: The function body is included in the book, but, is removed from this question for brevity.

7

For basic types, reference parameters usually offer no performance attribute at all.

Memory

For instance, in your example, the char requires a single byte. A reference type requires a pointer, which is usually either 4 or 8 bytes, where the size of a pointer directly correlates to the type of executable you are using (4 bytes for 32 bit, 8 bytes for 64 bit), due to the size of the memory register.

For most modern machines, this will be passed either on the stack, or in a register, as a single value. So it really makes no difference at all.

Time

Copying a basic type (char, int, long) is usually a single MOV instruction. Setting the pointer used in a reference parameter is, again, usually a single MOV instruction.

  • Why can't a reference just be an alternative name for the calling function's variable just as a reference is when it is defined in the same function as the variable accessing it? If it was, then no copying would be meccessary. – john01dav May 14 '16 at 0:36
  • @john01dav: That is possible if the function is treated with an function inlining optimization. Though not every function can be inlined, and when it isn't there has to be a manifestation of the formal parameter by the actual argument. – Erik Eidt May 14 '16 at 0:38
  • 1
    Because the function may well be compiled completely separately from the caller, so there's no way to compile it in a way that references the caller's variables. Instead, you have to point a pointer to it in a place the caller expects. In other words, do a MOV of some sort. – Gort the Robot May 14 '16 at 0:38
  • 2
    Further, the reference parameter incurs a (minor) memory reference cost by the callee, because the reference is what is passed instead of the actual (smaller or same size) value, so the callee has direct access to the address instead of the value. – Erik Eidt May 14 '16 at 0:45
  • 1
    Why are you worried about copying? With simple types, copying is cheaper than referencing. – Gort the Robot May 14 '16 at 4:04
7

There are two downsides to using references:

  1. Using references allows for additional aliasing. This means the compiler has to re-load and store the value more often unless it can in some other way determine that a value is not read / written between two uses in the code it sees.

  2. Every indirection means additional reading of memory.

In many cases, these two effects dominate any potential saving from not having to copy the argument, even if the type is slightly bigger than a pointer. Not that there is any such saving for trivially-copyable types no bigger than a pointer.

As an aside, since C++17 you should avoid passing a const std::string& in favor of a std::string_view:

The latter is more flexible and efficient, as it does not allocate the string, reduces indirection, and has no allocation-policy.

  • 1
    Surely the biggest downside is that references don't allow for automatic promotion so you may need different function implementations for char, unsigned char, short, unsigned short, uint16_t etc. Either that or the caller would have to copy the value to a temporary variable of the type expected by the function. Either way would cause massive bloat and/or inconvenience. – Dipstick Aug 24 '17 at 19:57
  • @Dipstick: A constant reference can bind to a temporary. If you meant the non-constant one, that one is not under discussion. – Deduplicator Aug 24 '17 at 20:01
  • 1
    Point 1 is a much better answer than the accepted one. – Joe Aug 27 '17 at 11:00
  • The aliasing can cause some really surprising behavior too. I once traced an infinite loop bug down to a "const &" parameter's value I was using as the loop termination match actually being changed via another alias within the loop. Removing the "&" fixed it. Sometimes non-reference parameters are the better call. – T.E.D. Jan 16 '18 at 20:29
-1

My understanding is that you want to "encapsulate" the arguments you pass to a function - the function should be able to modify only what you want it to modify. Always passing a reference and putting const in front of it is tedious and prone to errors.

Also, always passing references would mean creating a local variable for each function argument (instead of passing expressions).

In addition, passing a reference means the compiler will probably need to generate code for dereferencing in the called function. For some ABIs passing a reference may be significantly slower.

  • Can the downvoters explain why they downvoted? – martinkunev May 18 '16 at 9:07
  • Because the OP is explicitly comparing pass-by-value with pass-by-const-reference – Caleth Aug 24 '17 at 23:25
  • "Always passing a reference and putting const in front of it is tedious and prone to errors." - citation needed – Pharap May 30 '18 at 15:28
  • @Pharap It is one of those things that is easy to forget and often you won't get even a warning from the compiler when this happens. – martinkunev May 31 '18 at 10:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.