20

Seeing as C++11 supports move semantics, when initializing data members from arguments, should we attempt to move the value instead of copying it?

Here's an example showing how I would approach this in pre-C++11:

struct foo {
    std::vector<int> data;

    explicit foo(const std::vector<int>& data)
        : data(data)
    {
    }
};

Here, the copy constructor would be called.

In C++11, should we get into the habit of writing like this:

struct foo {
    std::vector<int> data;

    explicit foo(std::vector<int> data)
        : data(std::move(data))
    {
    }
};

Here, the move constructor would be called... as well as the copy constructor if the argument passed is an lvalue, but the benefit is that if an rvalue was passed, the move constructor would be called instead of the copy one.

I'm wondering if there's something I'm missing.

James McNellis
  • 348,265
  • 75
  • 913
  • 977
someguy
  • 7,144
  • 12
  • 43
  • 57

4 Answers4

8

My initial answer to your question was:

Don't copy data that you want to move. You can add a constructor using a rvalue reference, if performance is a problem:

explicit foo(std::vector<int>&& data)
    : data(std::move(data))            // thanks to Kerrek SB
{
}

Not exactly matching your question, but reading Rule-of-Three becomes Rule-of-Five with C++11? seems to be useful.

Edit:

However, the accepted answer to Passing/Moving parameters of a constructor in C++0x seems to advocate your approach, especially with more than one parameter. Otherwise there would be a combinatorial explosion of variants.

Community
  • 1
  • 1
René Richter
  • 3,839
  • 3
  • 34
  • 42
3

Passing by value in the copy constructor only helps when the argument is movable, otherwise you could end up with up to two copies (one for the argument passing, one for the member construction). So I'd say it's better to write a copy and a move constructor separately.

Passing by value makes sense for the assignment operator if you have a properly implemented swap function, though:

Foo & operator=(Foo other) { this->swap(std::move(other)); }

Now if other is moveable, Foo's move constructor comes in during argument construction, and if other is merely copyable, then the one necessary copy is made during argument construction, but in both cases you get to use the moving version of swap, which ought to be cheap. But this relies on the existence of a move constructor!

So note that out of "construction", "swap" and "assigment" you will have to implement two properly, and only the third can take advantage of the other two. Since swap should be no-throw, using the swap trick in the assigment operator is basically the only option.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • 2
    A decent compiler (pick any well-known compiler) will elide the copy, so even if the argument isn't movable, only one copy will take place. – someguy Aug 21 '11 at 13:11
3

Yes, you are doing it correctly. Any time you need a copy of a value, do it in the parameters by passing by value.

The following is correct:

struct foo {
    std::vector<int> data;

    explicit foo(std::vector<int> data)
        : data(std::move(data))
    {
    }
};
Benjamin Lindley
  • 101,917
  • 9
  • 204
  • 274
0

You should stick with:

struct foo {
    std::vector<int> data;

    explicit foo(const std::vector<int>& data)
        : data(data)
    {
    }
};

In which case "data" is only copied.

In the second case:

struct foo {
    std::vector<int> data;

    explicit foo(std::vector<int> data)
        : data(std::move(data))
    {
    }
};

"data" is first copied and then moved. Which is more expensive than just copying. Remember moving is not free, even though it probably is alot cheaper than copying.

On the other hand you might consider adding the following (in addition to or instead of the first).

struct foo {
    std::vector<int> data;

    explicit foo(std::vector<int>&& data)
        : data(std::move(data))
    {
    }
};

Where you know that "data" will not be used after the constructor call, and in which case you can just move it.

ronag
  • 49,529
  • 25
  • 126
  • 221
  • 2
    My comment from another answer: "A decent compiler (pick any well-known compiler) will elide the copy, so even if the argument isn't movable, only one copy will take place." I've considered having two separate constructors, but it may be going a bit overboard, especially with multiple types of constructors. – someguy Aug 21 '11 at 14:04
  • In the second case, if the constructor is passed an rvalue, it is not copied, it is only moved, or possibly elided. By passing it in by const ref, you are forcing there to be a copy, even when there doesn't have to be. – Benjamin Lindley Aug 21 '11 at 15:41