Why Pass by Const Reference Instead of by Value

Why pass by const reference instead of by value?

There are two main considerations. One is the expense of copying the passed object and the second is the assumptions that the compiler can make when the object is a a local object.

E.g. In the first form, in the body of f it cannot be assumed that a and b don't reference the same object; so the value of a must be re-read after any write to b, just in case. In the second form, a cannot be changed via a write to b, as it is local to the function, so these re-reads are unnecessary.

void f(const Obj& a, Obj& b)
{
// a and b could reference the same object
}

void f(Obj a, Obj& b)
{
// a is local, b cannot be a reference to a
}

E.g.: In the first example, the compiler may be able to assume that the value of a local object doesn't change when an unrelated call is made. Without information about h, the compiler may not know whether an object that that function has a reference to (via a reference parameter) isn't changed by h. For example, that object might be part of a global state which is modified by h.

void g(const Obj& a)
{
// ...
h(); // the value of a might change
// ...
}

void g(Obj a)
{
// ...
h(); // the value of a is unlikely to change
// ...
}

Unfortunately, this example isn't cast iron. It is possible to write a class that, say, adds a pointer to itself to a global state object in its constructor, so that even a local object of class type might be altered by a global function call. Despite this, there are still potentially more opportunities for valid optimizations for local objects as they can't be aliased directly by references passed in, or other pre-existing objects.

Passing a parameter by const reference should be chosen where the semantics of references are actually required, or as a performance improvement only if the cost of potential aliasing would be outweighed by the expense of copying the parameter.

When is a const reference better than pass-by-value in C++11?

The general rule of thumb for passing by value is when you would end up making a copy anyway. That is to say that rather than doing this:

void f(const std::vector<int>& x) {
std::vector<int> y(x);
// stuff
}

where you first pass a const-ref and then copy it, you should do this instead:

void f(std::vector<int> x) {
// work with x instead
}

This has been partially true in C++03, and has become more useful with move semantics, as the copy may be replaced by a move in the pass-by-val case when the function is called with an rvalue.

Otherwise, when all you want to do is read the data, passing by const reference is still the preferred, efficient way.

Why pass by value and not by const reference?

There are situations where you don't modify the input, but you still need an internal copy of the input, and then you may as well take the arguments by value. For example, suppose you have a function that returns a sorted copy of a vector:

template <typename V> V sorted_copy_1(V const & v)
{
V v_copy = v;
std::sort(v_copy.begin(), v_copy.end());
return v;
}

This is fine, but if the user has a vector that they never need for any other purpose, then you have to make a mandatory copy here that may be unnecessary. So just take the argument by value:

template <typename V> V sorted_copy_2(V v)
{
std::sort(v.begin(), v.end());
return v;
}

Now the entire process of producing, sorting and returning a vector can be done essentially "in-place".

Less expensive examples are algorithms which consume counters or iterators which need to be modified in the process of the algorithm. Again, taking those by value allows you to use the function parameter directly, rather than requiring a local copy.

benefits of passing const reference vs values in function in c++ for primitive types

In every ABI I know of, references are passed via something equivalent to pointers. So when the compiler cannot inline the function or otherwise must follow the ABI, it will pass pointers there.

Pointers are often larger than values; but more importantly, pointers do not point at registers, and while the top of the stack is almost always going to be in cache, what it points at may not. In addition, many ABIs have primitives passed via register, which can be faster than via memory.

The next problem is within the function. Whenever the code flow could possible modify an int, data from a const int& parameter must be reloaded! While the reference is to const, the data it refers to can be changed via other paths.

The most common ways this can happen is when you leave the code the complier can see while understanding the function body or modify memory through a global variable, or follow a pointer to touch an int elsewhere.

In comparison, an int argument whose address is not taken cannot be legally modified through other means than directly. This permits the compiler to understand it isn't being mutated.

This isn't just a problem for the complier trying to optimize and getting confused. Take something like:

struct ui{
enum{ defFontSize=9;};
std:optional<int> fontSize;
void reloadFontSize(){
fontSize=getFontSizePref();
fontSizeChanged(*fontSize),
}
void fontSizeChanged(int const& sz){
if(sz==defFontSize)
fontSize=std:nullopt;
else
fontSize=sz;
drawText(sz);
}
void drawText(int sz){
std::cout << "At size " << sz <<"\n";
}
};

and the optional, to whom we are passing a reference, gets destroyed and used after destruction.

A bug like this can be far less obvious than this. If we defaulted to passing by value, it could not happen.

Why not always pass by const reference in C++?

When an argument is passed by value it is modifiable and copying it may be elided. For example, the canonical way to implement the assignment operator looks like this:

T& T::operator= (T value) {
value.swap(*this);
return *this;
}

At first sight it may look inefficient because a T is being copied. However, it would be copied anyway, i.e., if a copy is needed one will be created either way:

T& T::operator= (T const& value) {
T(value).swap(*this); // has to do a copy right here
return *this;
}

However, for the first version, it may be possible not to create copy at all, for example

T f() { return T(); }
// ...
T x = ...;
x = f();

When assigning the result of f() which is of type T to x the compiler may decide that it doesn't need to copy the result of f() and instead pass it into the assignment operator directly. In that case, if the assignment operator takes the argument by const& the compiler has to create a copy inside the assignment operator. In the implementation taking the argument by value it can elide the copy! In fact, the return from f() can already elide the copy, i.e., the call to f() and the following assignment may just involve the default construction of the object! ... and for many modern compilers that is, indeed, the case!

Put differently: if you need to copy an argument, getting it passed by value may avoid the need to create a copy. Also, you can std::move() from value arguments but not from const& arguments.

Is it better in C++ to pass by value or pass by reference-to-const?

It used to be generally recommended best practice1 to use pass by const ref for all types, except for builtin types (char, int, double, etc.), for iterators and for function objects (lambdas, classes deriving from std::*_function).

This was especially true before the existence of move semantics. The reason is simple: if you passed by value, a copy of the object had to be made and, except for very small objects, this is always more expensive than passing a reference.

With C++11, we have gained move semantics. In a nutshell, move semantics permit that, in some cases, an object can be passed “by value” without copying it. In particular, this is the case when the object that you are passing is an rvalue.

In itself, moving an object is still at least as expensive as passing by reference. However, in many cases a function will internally copy an object anyway — i.e. it will take ownership of the argument.2

In these situations we have the following (simplified) trade-off:

  1. We can pass the object by reference, then copy internally.
  2. We can pass the object by value.

“Pass by value” still causes the object to be copied, unless the object is an rvalue. In the case of an rvalue, the object can be moved instead, so that the second case is suddenly no longer “copy, then move” but “move, then (potentially) move again”.

For large objects that implement proper move constructors (such as vectors, strings …), the second case is then vastly more efficient than the first. Therefore, it is recommended to use pass by value if the function takes ownership of the argument, and if the object type supports efficient moving.


A historical note:

In fact, any modern compiler should be able to figure out when passing by value is expensive, and implicitly convert the call to use a const ref if possible.

In theory. In practice, compilers can’t always change this without breaking the function’s binary interface. In some special cases (when the function is inlined) the copy will actually be elided if the compiler can figure out that the original object won’t be changed through the actions in the function.

But in general the compiler can’t determine this, and the advent of move semantics in C++ has made this optimisation much less relevant.


1 E.g. in Scott Meyers, Effective C++.

2 This is especially often true for object constructors, which may take arguments and store them internally to be part of the constructed object’s state.

Pass int by const reference or by value , any difference?

For primitive types, passing by value is much better than passing by reference. Not only is there no indirection, but with a reference, the compiler has to worry about potential aliasing, which can ruin optimization opportunities.

Finally, pass-by-reference causes lvalues to become odr-used, which can actually cause linker errors. And this final issue doesn't go away if the call gets inlined.

Why is it allowed to pass R-Values by const reference but not by normal reference?

For your final question:

how can a const reference keep pointing to an R-Value (anonymous variable)

Here is the answer. The C++ language says that a local const reference prolongs the lifetime of temporary values until the end of the containing scope, but saving you the cost of a copy-construction (i.e. if you were to use an local variable instead).

Does passing fundamental values by const reference really hurt performance?

On platforms where the fundamental type in question fits into a register, a decent compiler should eliminate const references from parameters if it can see both sides of the call. For templates that is usually a given (unless they were explicitly instantiated somewhere). Since your library presumably has to be templated all the way down, this will apply to your case.

It's possible that your end users will have bad compilers or platforms where e.g. a double does not fit into a register. I don't see why you'd be incentivized to make micro-optimizations for these particular users, but maybe you do.

It's also possible that you want to explicitly instantiate all templates in your library for some set of types and provide implementation-less header files. In that case the user's compiler must obey whatever calling conventions exist on that platform and will probably pass fundamental types by reference.

Ultimately, the answer is "profile the relevant and representative use cases" if you don't have faith in the compiler(s).


Edit (removed macro solution): As suggested by Jarod42, the C++ way would be using an alias template. This also avoids the lack of deduction that the asker was running into with their original approach:

template<class T>
using CONSTREF = const T&; // Or just T for benchmarking.

https://godbolt.org/z/mopZ6B

As cppreference says:

Alias templates are never deduced by template argument deduction when deducing a template template parameter.



Related Topics



Leave a reply



Submit