Warm tip: This article is reproduced from stackoverflow.com, please click
c++ reference

Most efficient way to take parameters in modern C++?

发布于 2020-03-27 10:25:12

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.

Questioner
user11313931
Viewed
15
Stack Danny 2019-07-03 21:20

There is a reason why all these combinations for parameter declaration exist. It just depends on what you need:

  • void foo(T obj);
    • the caller doesn't want obj to be modified [so it's copied]
    • foo can modify obj [it's a copy]
    • if 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);
    • the caller doesn't want obj to be modified [so it's copied]
    • foo can't modify obj
    • remember, 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);
    • the caller expects a change to obj
    • foo can modify obj
    • a reference means no copying. So this is useful for passing expensive-to-copy objects. However, this could be slow for small types (int, double etc.) meaning a pass-by-copy would be better.

  • void foo(const T& obj);
    • the caller doesn't want obj to be modified
    • foo can't modify obj
    • a reference means no copying. Since it's unmodifiable, this is the perfect way to parameterize large read-only containers. So: very cheap for expensive-to-copy objects, could be slow for small types.

  • void foo(T&& obj);
    • the caller doesn't care what happens to obj and has no problems if it's empty afterwards
    • foo 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 useful
    • however, this disallows lvalues to be passed as arguments.

There are many special cases so this is in-no-way a complete list.


some extra bits:

  • copy-swap-idiom
  • (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.
  • even when you don't std::move there is a lot of moving going on - assuming the type has the necessary operators.
  • Functions / Lambdas? Pass by value: 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:

parameter declaration flow chart