By Derek Thurn


2011-09-29 04:59:33 8 Comments

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?

4 comments

@Patrick Fromberg 2019-08-19 22:08:39

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.

@Edward Brey 2012-12-19 20:12:02

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.

@Ayjay 2011-09-29 06:01:25

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!

@Max Lybbert 2011-09-29 07:32:14

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 ( google-styleguide.googlecode.com/svn/trunk/… ).

@Ayjay 2011-09-29 07:42:43

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;

@Luc Touraille 2011-09-29 07:45:23

@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#).

@Max Lybbert 2011-09-29 07:51:40

Those are both valid points.

@Trevor Hickey 2013-09-24 23:28:09

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.

@Aaron McDaid 2013-10-22 16:53:31

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<foo> f) instead of bar(foo * f).

@Steven Lu 2014-04-06 10:10:48

@AaronMcDaid Isn't that even worse? The plain old non-const reference is a lot easier to deal with compared to the pointer...

@Aaron McDaid 2014-04-06 12:06:29

@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?)

@Julian 2015-01-07 19:37:20

@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 :)

@4LegsDrivenCat 2016-05-25 13:10:18

@Swiftflux Well, references can be null as well :) void f(A& r) {} A* p = nullptr; /* result of some find, for example */ f(*p);

@user79878 2017-04-27 18:19:45

Disagree with pass by value to take a copy. See answer below.

@Luc Danton 2011-09-29 05:15:57

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.

@Matthieu M. 2011-09-29 06:10:12

+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.

@Lucas 2012-07-26 17:16:28

@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.

@stijn 2013-05-28 14:01:04

+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..

@nurettin 2013-10-11 06:11:00

+1 So nothing changed since '93 in regards to this specific case.

@Aaron McDaid 2013-10-22 15:05:33

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 for more. Basically, & usually binds only to lvalues, but there's an exception for rvalue. There are alternatives.

@Aaron McDaid 2013-10-22 15:11:44

... I should have linked to this answer 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)'

@Luc Danton 2013-10-22 15:51:14

@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.

@Guillaume07 2013-12-26 14:41:00

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 ?

@Luc Danton 2013-12-26 14:57:16

@Guillaume07 'Doing the same' supposes that one of the goals is still to avoid unnecessary copies, in particular favouring (potential) moves over (unconditional) copies.

Related Questions

Sponsored Content

18 Answered Questions

[SOLVED] Why should C++ programmers minimize use of 'new'?

14 Answered Questions

[SOLVED] How do I return multiple values from a function?

5 Answered Questions

[SOLVED] What is the difference between 'typedef' and 'using' in C++11?

9 Answered Questions

[SOLVED] What is a lambda expression in C++11?

4 Answered Questions

[SOLVED] What does T&& (double ampersand) mean in C++11?

7 Answered Questions

13 Answered Questions

[SOLVED] Are the days of passing const std::string & as a parameter over?

  • 2012-04-19 15:20:57
  • Benj
  • 162884 View
  • 572 Score
  • 13 Answer
  • Tags:   c++ c++11

5 Answered Questions

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

  • 2014-07-03 00:48:24
  • tinlyx
  • 13888 View
  • 55 Score
  • 5 Answer
  • Tags:   c++ c++11

1 Answered Questions

Sponsored Content