59

I have some pre-C++11 code in which I use const references to pass large parameters like vector's a lot. An example is as follows:

int hd(const vector<int>& a) {
   return a[0];
}

I heard that with new C++11 features, you can pass the vector by value as follows without performance hits.

int hd(vector<int> a) {
   return a[0];
}

For example, this answer says

C++11's move semantics make passing and returning by value much more attractive even for complex objects.

Is it true that the above two options are the same performance-wise?

If so, when is using const reference as in option 1 better than option 2? (i.e. why do we still need to use const references in C++11).

One reason I ask is that const references complicate deduction of template parameters, and it would be a lot easier to use pass-by-value only, if it is the same with const reference performance-wise.

Community
  • 1
  • 1
thor
  • 21,418
  • 31
  • 87
  • 173
  • 14
    Passing by value makes no sense for the example you've shown. In general, pass an argument by value if the function is going to make a local copy. – Praetorian Jul 03 '14 at 00:52
  • 1
    How do `const` references complicate deduction of template parameters? – Rapptz Jul 03 '14 at 01:01
  • 3
    > Is it true that the above two options are the same performance-wise? No. They can be the same when the client passes in a prvalue. But otherwise, the costs are *dramatically* different. – Howard Hinnant Jul 03 '14 at 01:23
  • 4
    Don't forget the importance of const correctness. If the function isn't going to modify the argument, make it const, and if it's const, constant reference makes more sense. – Rob K Jul 03 '14 at 16:53

5 Answers5

75

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.

Martin Ba
  • 37,187
  • 33
  • 183
  • 337
Rapptz
  • 20,807
  • 5
  • 72
  • 86
  • 15
    Not only is otherwise passing by `const` reference fine, but much preferred. Passing by value to index and element is **strongly** contraindicated. – Howard Hinnant Jul 03 '14 at 01:20
  • It should be `std::vector &&x` if you want to relay on move semantics. – Samuel Jul 03 '14 at 07:09
  • 2
    @Samuel Passing by value would potentially invoke the move constructor on prvalues/xvalues and the copy constructor on lvalues. So passing the parameter by rvalue reference wouldn't get you the same semantics. – Rapptz Jul 03 '14 at 07:15
  • @Rapptz I think this is important in the case of the pass-by-value. As you say, with C++11, the caller is offered the opportunity of calling the function as `f(std::move(vec))` if they know that `vec` is no longer needed. In this case, no copies are made, C++03 didn't really offer you this facility. – Niall Jul 03 '14 at 07:52
  • 1
    Regarding your general note, I have read somewhere some-time ago, that passing built-in types by reference doesn't make much sense, so that `const int& x` doesn't make much sense over `int x` and the latter can be faster because copying those types is faster than going through indirection. Could you say something about it if you have the knowledge? – luk32 Jul 03 '14 at 11:42
  • 1
    @luk32 I was hoping for the same thing - most of the answers are focusing on the "standard" application of const refs - but what about types that entirely self-contained? Like a struct that contains only primitives. When is it worth it or not worth it to pass this struct by value vs by const ref? – Elliott Jul 07 '14 at 17:20
  • @Rapptz To add to what Niall said - in the case of a copyable type like std::vector, it doesn't make sense to force the user to make their object moveable; by taking by-value the function leaves it to them. For move-only types, taking by value *forces* them to move, *and* performs the move-construction into the local instance for you. Taking an rvalue-reference breaks the assumed result that after the `f( std::move(vec) )` call, `vec` is empty. Less relevant for `vec`, more relevant for move-only resource-owning objects. – boycy Jul 09 '14 at 08:45
11

There is a big difference. You will get a copy of a vector's internal array unless it was about to die.

int hd(vector<int> a) {
   //...
}
hd(func_returning_vector()); // internal array is "stolen" (move constructor is called)
vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8};
hd(v); // internal array is copied (copy constructor is called)

C++11 and the introduction of rvalue references changed the rules about returning objects like vectors - now you can do that (without worrying about a guaranteed copy). No basic rules about taking them as argument changed, though - you should still take them by const reference unless you actually need a real copy - take by value then.

cubuspl42
  • 7,833
  • 4
  • 41
  • 65
  • How do you conclude that C++11 rvalue references and move cons are not about taking them as arguments? What, exactly, do you propose the purpose of std::move is, except to promote an lvalue reference to an rvalue reference so it can be captured as an argument? http://en.cppreference.com/w/cpp/utility/move – kfsone Jul 10 '14 at 04:38
  • @kfsone By saying "it's all about..." I meant that for OP (the function author) no rules about taking objects like vector as arguments changed since C++98. Rules about returning objects changed, though - now you can return vectors without a guaranteed costful copy. The subroutine author doesn't care how do callers pass the arguments *to* the subroutine. When you take vector by const reference you don't care if the caller `move`ed a vector or whatever. I'll edit my anwser to make it more clear. – cubuspl42 Jul 10 '14 at 12:50
  • @kfsone And how do *you* conclude that C++11 rvalue references and move constructors *are* about taking objects as arguments? Have you changed a single letter of C++03 code **related to taking objects as arguments** because of the introduction of C++11? I don't think that a downvote was necessary (nevermind if yours or not). – cubuspl42 Jul 10 '14 at 13:13
  • Indeed I have. Just like turning them is optional, so is receiving them. I've seen more cases for changing function parameters than return values. What you return is already an rvalue, a temporary. RVO depends on it, with the compiler quietly detecting that the return is an rvalue that can be hoisted. And std::move is usually something I've only used in calling functions - I've yet to use `return std::move(something)`. – kfsone Jul 11 '14 at 11:04
  • `std::vector v; v.reserve(count + 2); v.emplace_back(x); v.emplace_back(y); /*...*/ foo(std::move(v));` – kfsone Jul 11 '14 at 11:08
9

C++11's move semantics make passing and returning by value much more attractive even for complex objects.

The sample you give, however, is a sample of pass by value

int hd(vector<int> a) {

So C++11 has no impact on this.

Even if you had correctly declared 'hd' to take an rvalue

int hd(vector<int>&& a) {

it may be cheaper than pass-by-value but performing a successful move (as opposed to a simple std::move which may have no effect at all) may be more expensive than a simple pass-by-reference. A new vector<int> must be constructed and it must take ownership of the contents of a. We don't have the old overhead of having to allocate a new array of elements and copy the values over, but we still need to transfer the data fields of vector.

More importantly, in the case of a successful move, a would be destroyed in this process:

std::vector<int> x;
x.push(1);
int n = hd(std::move(x));
std::cout << x.size() << '\n'; // not what it used to be

Consider the following full example:

struct Str {
    char* m_ptr;
    Str() : m_ptr(nullptr) {}
    Str(const char* ptr) : m_ptr(strdup(ptr)) {}
    Str(const Str& rhs) : m_ptr(strdup(rhs.m_ptr)) {}
    Str(Str&& rhs) {
      if (&rhs != this) {
        m_ptr = rhs.m_ptr;
        rhs.m_ptr = nullptr;
      }
    }
    ~Str() {
      if (m_ptr) {
        printf("dtor: freeing %p\n", m_ptr)
        free(m_ptr);
        m_ptr = nullptr;
      }
    }
};

void hd(Str&& str) {
  printf("str.m_ptr = %p\n", str.m_ptr);
}

int main() {
  Str a("hello world"); // duplicates 'hello world'.
  Str b(a); // creates another copy
  hd(std::move(b)); // transfers authority for b to function hd.
  //hd(b); // compile error
  printf("after hd, b.m_ptr = %p\n", b.m_ptr); // it's been moved.
}

As a general rule:

  • Pass by value for trivial objects,
  • Pass by value if the destination needs a mutable copy,
  • Pass by value if you always need to make a copy,
  • Pass by const reference for non-trivial objects where the viewer only needs to see the content/state but doesn't need it to be modifiable,
  • Move when the destination needs a mutable copy of a temporary/constructed value (e.g. std::move(std::string("a") + std::string("b"))).
  • Move when you require locality of the object state but want to retain existing values/data and release the current holder.
kfsone
  • 23,617
  • 2
  • 42
  • 74
  • 1
    Are you suggesting that rvalue references are slower than lvalue references to const? It seems you mixed up some concepts (rvalues, rvalue references, move semantics) here. – fredoverflow Jul 03 '14 at 08:15
  • 3
    Why are you using `std::move( ... + ... )` in your rule #5? It does not hurt, but you do not need to `std::move()` the result of the concatenation. – blackbird Sep 20 '15 at 22:23
7

Remember that if you are not passing in an r-value, then passing by value would result in a full blown copy. So generally speaking, passing by value could lead to a performance hit.

zdan
  • 28,667
  • 7
  • 60
  • 71
3

Your example is flawed. C++11 does not give you a move with the code that you have, and a copy would be made.

However, you can get a move by declaring the function to take an rvalue reference, and then passing one:

int hd(vector<int>&& a) {
   return a[0];
}

// ...
std::vector<int> a = ...
int x = hd(std::move(a));

That's assuming that you won't be using the variable a in your function again except to destroy it or to assign to it a new value. Here, std::move casts the value to an rvalue reference, allowing the move.

Const references allow temporaries to be silently created. You can pass in something that is appropriate for an implicit constructor, and a temporary will be created. The classic example is a char array being converted to const std::string& but with std::vector, a std::initializer_list can be converted.

So:

int hd(const std::vector<int>&); // Declaration of const reference function
int x = hd({1,2,3,4});

And of course, you can move the temporary in as well:

int hd(std::vector<int>&&); // Declaration of rvalue reference function
int x = hd({1,2,3,4});
Matthew Lundberg
  • 42,009
  • 6
  • 90
  • 112
  • 4
    Whether or not an object is moved into a function has nothing to do with whether the parameter is declared as a rvalue reference. If it is a temporary, the compiler will move the object into the parameter or elide the copy altogether (see http://ideone.com/uXCXcq). – kloffy Jul 03 '14 at 06:09
  • Your first version `int hd(vector&& a);` cannot be called with `a` as suggested by the first chunk of code, since you cannot bind the lvalue `a` to a rvalue reference. So, using `std::move(a)` is really not just an option ("Or cast the value to an rvalue reference..."), but compulsory if there is only this declaration of `hd()`. – blackbird Sep 20 '15 at 22:17