44

I was wondering in which situations I still need to use const references in parameters since C++11. I don't fully understand move semantics but I think this is a legit question. This question is meant for only the situations where a const reference replaces a copy being made while it is only needed to "read" the value (e.g. usage of const member functions).

Normally I would write a (member)function like this:

#include <vector>

template<class T>
class Vector {
    std::vector<T> _impl;
public:
    void add(const T& value) {
         _impl.push_back(value);
    }
};

But I'm thinking that it's safe to assume that to compiler would optimize it using move semantics if I write it like this and class T ofcourse implements a move constructor:

#include <vector>

template<class T>
class Vector {
    std::vector<T> _impl;
public:
    void add(T value) {
         _impl.push_back(value);
    }
};

Am I right? If so, is it safe to assume it can be used in any situation? If not, I would like to know which. This would make life much easier since I wouldn't have to implement an class specialization for fundamental types for example, besides it looks much cleaner.

Micha Wiedenmann
  • 19,979
  • 21
  • 92
  • 137
Tim
  • 5,521
  • 8
  • 36
  • 69
  • possible duplicate of [Is it better in C++ to pass by value or pass by constant reference?](http://stackoverflow.com/questions/270408/is-it-better-in-c-to-pass-by-value-or-pass-by-constant-reference) – stijn Jul 14 '13 at 19:07
  • 1
    `vector::push_back` makes copies. You're looking for `emplace_back`. – Cubic Jul 14 '13 at 19:40
  • 4
    @stijn I can't find the similair situation regarding move semantics in that question. – Tim Jul 14 '13 at 19:57
  • @Cubic I want to make a copy in this situation when pushing it into the back of the `vector`. I'm wondering if the compiler is able to eliminate the second copy through move semantics (see second example). – Tim Jul 14 '13 at 19:59

1 Answers1

46

The solution you propose:

void add(T value) {
     _impl.push_back(value);
}

Would require some adjustment, since this way you do always end up performing one copy of value, even if you pass an rvalue to add() (two copies if you pass an lvalue): since value is an lvalue, the compiler won't automatically move from it when you pass it as an argument to push_back.

Instead, you should do this:

void add(T value) {
     _impl.push_back(std::move(value));
//                   ^^^^^^^^^
}

This is better, but still not sufficiently good for template code, because you do not know if T is cheap or expensive to move. If T is a POD like this:

struct X
{
    double x;
    int i;
    char arr[255];
};

Then moving it won't be any faster than copying it (in fact, moving it would be the same thing as copying it). Because your generic code is supposed to avoid unnecessary operations (and that's because those operations may be expensive for some types), you cannot afford taking the parameter by value.

One possible solution (the one adopted by the C++ standard library) is to provide two overloads of add(), one taking an lvalue reference and one taking an rvalue reference:

void add(T const& val) { _impl.push_back(val); }
void add(T&& val) { _impl.push_back(std::move(val)); }

Another possibility is to provide a (possibly SFINAE-constrained) perfect-forwarding template version of add() that would accept a so-called universal reference (non-standard term coined by Scott Meyers):

template<typename U>
void add(U&& val) { _impl.push_back(std::forward<U>(val)); }

Both these solutions are optimal in the sense that only one copy is performed when lvalues are provided, and only one move is performed when rvalues are provided.

Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • Thanks just what I needed! About "but still not sufficiently good for template code" and "moving it would be the same thing as copying it", isn't that exactly the opposite of the first quoted statement of yours? I mean, isn't this exactly what I want to achieve, fundamental types without overhead using the same method. – Tim Jul 14 '13 at 23:41
  • @Tim: I do not see a contradiction there. "Still not sufficiently good for template code" refers to the function taking its parameter by value, and it is not sufficiently good in general because the parameter may have a type which may be expensive to (copy or) move, so you want to avoid any extra (copy or) move. `X` is meant to provide an example of such a type (actually, whether that's actually "expensive" depends on the client's requirements, but let's not indulge in this kind of considerations). – Andy Prowl Jul 15 '13 at 00:00
  • I understand what you're saying. I may not have been clear, `std::vector::push_back` is already making a copy right? So if, and only if, `std::vector::push_back` could take the moved or copied value, in case of fundamental types, directly (which I doubt since it's taking a const reference), it would be sufficiently good for template code. Or am I missing something? – Tim Jul 15 '13 at 00:07
  • 1
    @Tim: I'm not sure I understand. There are two overloads of push_back(), one always copies and one always moves, and they both take a reference. If you take by value, the function argument will be either copied or moved (depending on its value category) into the function parameter - and this is the extra operation that you should avoid when dealing with template code, since you don't know a priori how expensive that operation is – Andy Prowl Jul 15 '13 at 00:25
  • Wouldn't `emplace_back` be better if the type passed in was a non-`T` for which `T` has a move-from constructor? – Yakk - Adam Nevraumont Jul 15 '13 at 18:40
  • for last code sample, of course read `emplace_back` instead of `push_back` – norisknofun Dec 11 '18 at 19:53
  • 1
    Is the explicit `std::move` in the second line of the first possible solution, i.e. `void add(T&& val) { _impl.push_back(std::move(val)); }` actually necessary? `val` is already of type `T&&`, i.e. an rvalue reference. Hence, the correct overloaded version of `push_back` which moves its argument should even be called without the explicit `std::move`? – user2690527 Jun 08 '20 at 09:35