145

In traditional C++, passing by value into functions and methods is slow for large objects, and is generally frowned upon. Instead, C++ programmers tend to pass references around, which is faster, but which introduces all sorts of complicated questions around ownership and especially around memory management (in the event that the object is heap-allocated)

Now, in C++11, we have Rvalue references and move constructors, which mean that it's possible to implement a large object (like an std::vector) that's cheap to pass by value into and out of a function.

So, does this mean that the default should be to pass by value for instances of types such as std::vector and std::string? What about for custom objects? What's the new best practice?

Keith Pinson
  • 7,835
  • 7
  • 61
  • 104
Derek Thurn
  • 14,953
  • 9
  • 42
  • 64
  • 23
    `pass by reference ... which introduces all sorts of complicated questions around ownership and especially around memory management (in the event that the object is heap-allocated)`. I don't understand how it's complicated or problematic for ownership? May be I missed something ? – iammilind Sep 29 '11 at 05:02
  • 1
    @iammilind: An example from personal experience. One thread has a string object. It is passed to a function which spawns another thread, but unknown to the caller the function took the string as `const std::string&` and not a copy. The first thread then exited... – Zan Lynx Sep 29 '11 at 05:11
  • 12
    @ZanLynx: That sounds like a function that was clearly never designed to be called as a thread function. – Nicol Bolas Sep 29 '11 at 05:13
  • 5
    Agreeing with iammilind, I don't see any problem. Passing by const reference should be your default for "large" objects, and by value for smaller objects. I'd put the limit between large and small at around 16 bytes (or 4 pointers on a 32 bit system). – J.N. Sep 29 '11 at 05:17
  • 3
    Herb Sutter's [Back to the Basics! Essentials of Modern C++ presentation](https://github.com/CppCon/CppCon2014/blob/master/Presentations/Back%20to%20the%20Basics!%20Essentials%20of%20Modern%20C%2B%2B%20Style/Back%20to%20the%20Basics!%20Essentials%20of%20Modern%20C%2B%2B%20Style%20-%20Herb%20Sutter%20-%20CppCon%202014.pdf) at CppCon went into quite a bit of detail on this. [Video here](http://www.youtube.com/watch?v=xnqTKD8uD64). – Chris Drew Oct 02 '14 at 08:08
  • *Related:* [How to pass objects to functions in C++?](http://stackoverflow.com/q/2139224/183120) – legends2k Apr 02 '15 at 11:00
  • Say, I have a sink argument (e.g. string to be overwritten on the next step) into function that will return say substr of the string passed in. If I pass it by value it will be copy constructed (including memory allocation and data copy) first and then substring will be constructed from copy. Do you mean that this copy will be eliminated by compiler? – uuu777 Sep 13 '19 at 12:52

4 Answers4

142

It's a reasonable default if you need to make a copy inside the body. This is what Dave Abrahams is advocating:

Guideline: Don’t copy your function arguments. Instead, pass them by value and let the compiler do the copying.

In code this means don't do this:

void foo(T const& t)
{
    auto copy = t;
    // ...
}

but do this:

void foo(T t)
{
    // ...
}

which has the advantage that the caller can use foo like so:

T lval;
foo(lval); // copy from lvalue
foo(T {}); // (potential) move from prvalue
foo(std::move(lval)); // (potential) move from xvalue

and only minimal work is done. You'd need two overloads to do the same with references, void foo(T const&); and void foo(T&&);.

With that in mind, I now wrote my valued constructors as such:

class T {
    U u;
    V v;
public:
    T(U u, V v)
        : u(std::move(u))
        , v(std::move(v))
    {}
};

Otherwise, passing by reference to const still is reasonable.

Luc Danton
  • 34,649
  • 6
  • 70
  • 114
  • 29
    +1, especially for the last bit :) One should not forget that Move Constructors can only be invoked if the object to move from is not expected to be unchanged afterward: `SomeProperty p;` `for (auto x: vec) { x.foo(p); }` does not fit, for example. Also, Move Constructors have a cost (the larger the object, the higher the cost) while `const&` are essentially free. – Matthieu M. Sep 29 '11 at 06:10
  • 25
    @MatthieuM. But it's important to know what "the larger the object, the higher the cost" of the move actually means: "larger" actually means "the more member variables it has". For instance, moving an `std::vector` with a million elements _costs the same_ as moving one with five elements since only the pointer to the array on the heap is moved, not every object in the vector. So it's not actually that big of an issue. – Lucas Jul 26 '12 at 17:16
  • +1 I also tend to use the pass-by-value-then-move construct since I started using C++11. This makes me feel somewhat uneasy though, since my code now has `std::move` all over the place.. – stijn May 28 '13 at 14:01
  • +1 So nothing changed since '93 in regards to this specific case. – nurettin Oct 11 '13 at 06:11
  • 1
    There is one risk with `const&`, that has tripped me up a few times. `void foo(const T&); int main() { S s; foo(s); }`. This can compile, even though the types are different, if there is a T constructor that takes an S as argument. This can be slow, because a large T object may be getting constructed. You might *think* your passing a reference without copying, but may you are. See [this answer to a question I asked](http://stackoverflow.com/a/9088623/146041) for more. Basically, `&` usually binds only to lvalues, but there's an exception for `rvalue`. There are alternatives. – Aaron McDaid Oct 22 '13 at 15:05
  • ... I should have linked to [this answer](http://stackoverflow.com/a/9089311/146041) instead/aswell. Also, I I should have finished by saying "there's an exception for `const&`, allowing it to bind to rvalues (e.g. temporary copies)' – Aaron McDaid Oct 22 '13 at 15:11
  • 1
    @AaronMcDaid This is old news, in the sense that's something you always had to be aware of even before C++11. And nothing much has changed with respect to that. – Luc Danton Oct 22 '13 at 15:51
  • I don't get this sentence : " You'd need two overloads to do the same with references, void foo(T const&); and void foo(T&&);" because foo(T const&) can handle lvalue,xvalue,prvalue, so why do you say we have to use two overloads to achieve the same result ? – Guillaume Paris Dec 26 '13 at 14:41
  • @Guillaume07 'Doing the same' supposes that one of the goals is still to avoid unnecessary copies, in particular favouring (potential) moves over (unconditional) copies. – Luc Danton Dec 26 '13 at 14:57
  • Why do we ever want to make a copy? I believe it's only a good idea if copy-elision is done, so eventially there is no copy. – jaques-sam Jan 14 '20 at 09:32
75

In almost all cases, your semantics should be either:

bar(foo f); // want to obtain a copy of f
bar(const foo& f); // want to read f
bar(foo& f); // want to modify f

All other signatures should be used only sparingly, and with good justification. The compiler will now pretty much always work these out in the most efficient way. You can just get on with writing your code!

Ayjay
  • 3,413
  • 15
  • 20
  • 3
    Although I prefer passing a pointer if I'm going to modify an argument. I agree with the Google style guide that this makes it more obvious that the argument will be modified without needing to double-check the function's signature ( http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml?showone=Reference_Arguments#Reference_Arguments ). – Max Lybbert Sep 29 '11 at 07:32
  • 41
    The reason that I dislike passing pointers is that it adds a possible failure state to my functions. I try to write all my functions so that they are provably correct, as it vastly reduces the space for bugs to hide in. `foo(bar& x) { x.a = 3; }` is a heck of a lot more reliable (and readable!) than `foo(bar* x) {if (!x) throw std::invalid_argument("x"); x->a = 3;` – Ayjay Sep 29 '11 at 07:42
  • 22
    @Max Lybbert: With a pointer parameter, you don't need to check the function's signature, but you need to check the documentation to know if you're allowed to pass null pointers, if the function will take ownsership, etc. IMHO, a pointer parameter conveys much less informations than a non-const reference. I agree however that it would be nice to have a visual clue at the call site that the argument may be modified (like the `ref` keyword in C#). – Luc Touraille Sep 29 '11 at 07:45
  • In regards to passing by value and relying on move semantics, I feel these three choices do a better job of explaining the intended use of the parameter. These are the guidelines I always follow as well. – Trevor Hickey Sep 24 '13 at 23:28
  • Regarding the use of pointers, you could write a simple smart-pointer type that will perform that check for you, @Ayjay. We could call it `out<>` for 'out param'. Then the function signature would be `bar(out f)` instead of `bar(foo * f)`. – Aaron McDaid Oct 22 '13 at 16:53
  • @AaronMcDaid Isn't that **even worse**? The plain old non-const reference is a lot easier to deal with compared to the pointer... – Steven Lu Apr 06 '14 at 10:10
  • @StevenLu, that might require that the call site take responsibility for making sure it doesn't dereference a null pointer when passing to a function that takes the arg by reference. At some stage, some of your pointers need to be checked for null, and I'm suggesting you could build that check into a smart pointer. But yeah, having to type `->` instead of `.` is annoying :-) (Actually, is `shared_ptr` intended to never be null? Much as (I think) `unique_ptr` is?) – Aaron McDaid Apr 06 '14 at 12:06
  • 1
    @AaronMcDaid `is shared_ptr intended to never be null? Much as (I think) unique_ptr is?` Both of those assumptions are incorrect. `unique_ptr` and `shared_ptr` can hold null/`nullptr` values. If you don't want to worry about null values, you should be using references, because they can never be null. You also won't have to type `->`, which you find to be annoying :) – Julian Jan 07 '15 at 19:37
  • @Swiftflux Well, references can be null as well :) `void f(A& r) {} A* p = nullptr; /* result of some find, for example */ f(*p);` – 4LegsDrivenCat May 25 '16 at 13:10
  • Disagree with pass by value to take a copy. See answer below. – user79878 Apr 27 '17 at 18:19
10

Pass parameters by value if inside the function body you need a copy of the object or only need to move the object. Pass by const& if you only need non-mutating access to the object.

Object copy example:

void copy_antipattern(T const& t) { // (Don't do this.)
    auto copy = t;
    t.some_mutating_function();
}

void copy_pattern(T t) { // (Do this instead.)
    t.some_mutating_function();
}

Object move example:

std::vector<T> v; 

void move_antipattern(T const& t) {
    v.push_back(t); 
}

void move_pattern(T t) {
    v.push_back(std::move(t)); 
}

Non-mutating access example:

void read_pattern(T const& t) {
    t.some_const_function();
}

For rationale, see these blog posts by Dave Abrahams and Xiang Fan.

Edward Brey
  • 40,302
  • 20
  • 199
  • 253
0

The signature of a function should reflect it's intended use. Readability is important, also for the optimizer.

This is the best precondition for an optimizer to create fastest code - in theory at least and if not in reality then in a few years reality.

Performance considerations are very often overrated in the context of parameter passing. Perfect forwarding is an example. Functions like emplace_back are mostly very short and inlined anyway.

Patrick Fromberg
  • 1,313
  • 11
  • 37