2

I have tested the move Semantic in C++11. I wrote a class with a move constructor.

class DefaultConstructor
{
public:
    DefaultConstructor(std::vector<int> test) :
        m_vec(std::forward<std::vector<int>>(test))
    {

    };

    DefaultConstructor(DefaultConstructor &&def) :
        m_vec(std::forward<std::vector<int>>(def.m_vec))
    {
    }

    DefaultConstructor& operator=(DefaultConstructor&& def) {
        m_vec = std::move(def.m_vec);
        return *this;
    }

    DefaultConstructor& operator=(const DefaultConstructor&) = delete;
    DefaultConstructor(DefaultConstructor &) = delete;

    std::vector<int> m_vec;
};

I wrote a main function that use the move semantic. I understand what happend in the move semantic and it is great tool. But there is some behavior which is for me not explainable. When I call in the main function DefaultConstructor testConstructor2 = std::move(testConstructor); for me the DefaultConstructor& operator=(DefaultConstructor&& def) should called. But the Visual Studio 2015 calls the move constructor.

int main()
{
    std::vector<int> test = { 1, 2, 3, 4, 5 };
    DefaultConstructor testConstructor(std::move(test));

    DefaultConstructor testConstructor2 = std::move(testConstructor);
    DefaultConstructor &testConstructor3 = DefaultConstructor({ 6, 7, 8, 9 });
    DefaultConstructor testConstructor4 = std::move(testConstructor3);
    swapMove(testConstructor, testConstructor2);
}

Okay I thought maybe the = Move Operator is not necessary anymore. But I tried a SwapMove function. This function calls the = move Operator.

template<typename T>
void swapMove(T &a, T &b)
{
    T tmp(std::move(a));
    a = std::move(b);
    b = std::move(tmp);
}

Can someone explain what exactly is the difference betwenn the two calls? Shouldn't be the calls a = std::move(b); and DefaultConstructor testConstructor2 = std::move(testConstructor); have the same behavior?

PeterNL
  • 630
  • 6
  • 21
  • 8
    `DefaultConstructor testConstructor2 = std::move(testConstructor);` is initialisation, not assignment. – Jonathan Potter Aug 31 '15 at 20:42
  • Declare `testConstructor2` separately beforehand, e.g. `DefaultConstructor testConstructor2({});` – Red Alert Aug 31 '15 at 20:43
  • Jonatha Potter looks like this is the answer. Thx. – PeterNL Aug 31 '15 at 20:45
  • 1
    [OT] You want to use `std::move()` in your move constructor, not `std::forward()`. – Barry Aug 31 '15 at 20:51
  • `std::forward` decides for me if the copy or the move semantic get used. – PeterNL Aug 31 '15 at 20:55
  • 1
    There is nothing to decide, you've explicitly said that `def` is an rvalue-reference. `std::forward` is only useful when templates + reference collapsing are used. – Red Alert Aug 31 '15 at 20:59
  • `std::vector test = { 1, 2, 3, 4, 5 }; DefaultConstructor testConstructor(test);` has a different behavior then `std::vector test = { 1, 2, 3, 4, 5 }; DefaultConstructor testConstructor(std::move(test));` http://stackoverflow.com/questions/9671749/whats-the-difference-between-stdmove-and-stdforward?rq=1 – PeterNL Aug 31 '15 at 21:01
  • @PeterNL Yes, because the first one uses the copy constructor (which is `delete`d) and the second uses the move constructor. That has nothing to do with the difference between `std::move` (cast to rvalue) and `std::forward` (conditional cast to rvalue - which in your case you are doing unconditionally). – Barry Sep 01 '15 at 00:57

3 Answers3

8

The syntax

 DefaultConstructor testConstructor2 = something;

always invokes a constructor because the object testConstructor2 does not exist yet. operator= can only be called in the context of an object that has already been constructed.

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

This:

T foo = bar;

is called copy-initialization. It is typically, but not always, equivalent to:

T foo(bar);

The difference is that the latter is a direct function call to T's constructor, whereas the former tries to construct an implicit conversion sequence from decltype(bar) to T. There are thus cases where direct-initialization succeeds but copy-initialization can fail. Either way, initialization is initialization: it's a constructor call, not an assignment call.

In our case though, these two lines are exactly equivalent:

DefaultConstructor testConstructor2 = std::move(testConstructor);
DefaultConstructor testConstructor2{std::move(testConstructor)};

and neither one of them calls DefaultConstructor::operator=.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • 1
    Just out of curiosity, when would `T foo = bar;` not be equivalent to `T foo(bar)`? – PC Luddite Aug 31 '15 at 20:47
  • 3
    @PCLuddite When you need multiple conversions. e.g.: `struct A { A(int ) { } }; struct B { B(A ) { } };` Given that, `B b(4);` succeeds but `B b = 4;` will not. – Barry Aug 31 '15 at 20:49
  • 2
    @PCLuddite Also when you have an `explicit` constructor that's a better match (or the only possible match, which makes the `=` case an error). `struct A { explicit A(bool) { /* #1 */ } A(int) { /* #2 */ } }; A a = true; /* calls #2 */ A b(false); /* calls #1 */` and when the class in question is not copyable/movable: `struct B { B(B&&) = delete; B(int) {} }; B b(1); /* OK */ B b2 = 1; /* error */` – T.C. Aug 31 '15 at 22:43
2

DefaultConstructor testConstructor2 = std::move(testConstructor); is construction, not assignment. It's exactly analogous to copy construction vs assignment in the same sort of code pre C++11.

Mark B
  • 95,107
  • 10
  • 109
  • 188