10

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.

1

4 Answers 4

10

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.

8
  • 1
    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, 2016 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, 2016 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. May 14, 2016 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, 2016 at 0:45
  • 2
    Why are you worried about copying? With simple types, copying is cheaper than referencing. May 14, 2016 at 4:04
9

There are two major 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 any type which uses small-buffer-optimization, like most versions of std::string, there is an additional reason:

  1. Getting the start and size of the sequence is not just a trivial memory read, but depends on whether the internal buffer is engaged, or external space was needed.

In many cases, these 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.

4
  • 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, 2017 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. Aug 24, 2017 at 20:01
  • 1
    Point 1 is a much better answer than the accepted one.
    – Joe
    Aug 27, 2017 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, 2018 at 20:29
1

Your first parameter s, you don’t really want a const reference, you want a value. For example, s bring a reference means the string could me modified or deleted by another thread.

The only reason why you pass it as a const reference is performance. For c there is no performance gain by passing it as a reference so it’s not done. “occurs” is a reference because you want two return values in your function but you can use only one. I actually dislike this very much because a variable can/will be modified without anything visible in the call.

If you look at a language like Swift that is about 30 years newer. String is passed by value - but creating a copy is fast by using copy-on-write. A function can return multiple values. So no references are needed at all.

3
  • Well, C++ allowed for non-contiguous and even shared strings. Once upon a time. But they were forbidden in C++11 for a reason. And yes, that necessitates a slight adjustment if performance might be relevant. Jan 25, 2021 at 20:58
  • @Deduplicator std::string has requirements that preclude being copy-on-write, but C++ doesn't forbid copy-on-write strings if you want them
    – Caleth
    Jan 27, 2021 at 11:27
  • 1
    "occurs is a reference because you want two return values in your function but you can use only one." -> but you can return a struct { size_t pos; size_t occurs }, or std::pair<size_t, size_t> or w/e
    – Caleth
    Jan 27, 2021 at 11:30
-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.

4
  • Can the downvoters explain why they downvoted? May 18, 2016 at 9:07
  • Because the OP is explicitly comparing pass-by-value with pass-by-const-reference
    – Caleth
    Aug 24, 2017 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, 2018 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. May 31, 2018 at 10:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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