Parameters can be taken in many different ways:
void foo(T obj);
void foo(const T obj);
void foo(T& obj);
void foo(const T& obj);
void foo(T&& obj);
void foo(const T&& obj);
I didn't include taking a parameter via a pointer because a pointer could already be the type T
.
The first two ways are taking the object by value which will cause the object to get copied (if not optimized by a smart compiler).
The other ways take the object by reference (the first two by a l-value reference and the last two as a r-value reference) which should omit making any copies. The const
qualified versions promise not to modify the referenced object.
If I want my function to be able to take as parameters all kinds of values (variables, temporaries returned from functions, literals etc.) what should be its prototype for a not-object-modifying version (const
) and a potentially-object-modifying version (not const
) version, if I want it to be the most efficent (avoid invoking copy constructors, move constructors, assignment operators, creating temporaries etc.)?
I'm not sure which type of references should I use. If this is a subjective question I am looking for pros and cons of each approach.
There is a reason why all these combinations for parameter declaration exist. It just depends on what you need:
void foo(T obj);
obj
to be modified [so it's copied]foo
can modify obj
[it's a copy]foo
doesn't modify obj
this is still prefered over const T&
- assuming T
is small (fits 1-2 CPU registers)void foo(const T obj);
obj
to be modified [so it's copied]foo
can't modify obj
const
is often to help you find errors. That's why this is generally used to avoid accidentely modifying obj
. (e.g. if (obj = 5)
)void foo(T& obj);
obj
foo
can modify obj
int
, double
etc.) meaning a pass-by-copy would be better.void foo(const T& obj);
obj
to be modifiedfoo
can't modify obj
void foo(T&& obj);
obj
and has no problems if it's empty afterwardsfoo
can modify obj
by stealing the data by moving the information to another place.void foo(const T&& obj);
foo
can't modify obj
, which makes this rarely usefulThere are many special cases so this is in-no-way a complete list.
some extra bits:
(const T& obj)
is often worse than just (T obj)
for many reasons. But remember the caller can always let T
simply be std::reference_wrapper<const T>
to avoid copying. This could break the function, though.std::move
there is a lot of moving going on - assuming the type has the necessary operators.template <typename F> void execute(F f) { f(); }
Finally, worth sharing is this flow chart made by Bisqwit from this video which makes for a nice graphic: