2

I'm currently trying to get a hang of move constructor. I came upon the following (Compiled using g++ d.cpp --std=c++11 -O3)

class A {
    string _x;
public:
  A(string x) { cout << "default contrsutctor: " << x << "\n"; _x = x; }
  A(const A& other) { cout << "copy contrsutctor: " << other._x << "\n"; _x = other._x;  }
  A(A&& other) { cout << "move contrsutctor: " << other._x << "\n"; _x = other._x;  }

  A foo() {
    cout << "foo: " << _x << "\n";
    return A("foo");
  }
};

int main()
{
  A a;
  A b = a;
  b.foo();
}

I expect this to output:

default contrsutctor: a
move contrsutctor: a
foo: a
default contrsutctor: foo

However the output is:

default contrsutctor: a
copy contrsutctor: a
foo: a
default contrsutctor: foo

Why isn't the A b = a line optimized to use the move constructor? The a object is never used afterwards, so it would be safe to optimize the code to use it instead of the copy constructor.

I know I could force the move contructor to be invoked with std::move(), but I'd prefer this to happen automatically in cases like this one.

Matthew Murdoch
  • 30,874
  • 30
  • 96
  • 127
Ákos Vandra-Meyer
  • 1,890
  • 1
  • 23
  • 40
  • `A b = a;` tells the compiler to construct b by copying a, not move a to b. – Mine Apr 10 '14 at 08:18
  • 2
    possible duplicate of [Do C++11 compilers turn local variables into rvalues when they can during code optimization?](http://stackoverflow.com/questions/21830894/do-c11-compilers-turn-local-variables-into-rvalues-when-they-can-during-code-o) – juanchopanza Apr 10 '14 at 08:47

3 Answers3

7

Why isn't the A b = a line optimized to use the move constructor?

What you can do in copy constructor and move constructor could be totally different. The compiler cannot guarantee that the results of the two constructors are identical. Implementing this kind of optimization has the potential of changing the behavior of your program, which breaks the as-if rule.

You need to use std::move to cast a to A&&:

#include <utility>
int main()
{
  A a("a");
  A b = std::move(a);
  b.foo();
}

A correct implementation of the move constructor should be:

A(A&& other)
: _x(std::move(other._x))
{}

After the line A b = std::move(a);, a should be "empty". In this case, a._x will be empty. as pointed by @TonyD in the comments, a._str could be in an unspecified but valid state (move constructor of std:string). You should use a with caution after this line.

Danqi Wang
  • 1,597
  • 1
  • 10
  • 28
  • 2
    Another correct implementation: `A(A&&) = default;` :) – Matthieu M. Apr 10 '14 at 09:19
  • 2
    "After the line `A b = std::move(a); a` should be "empty". In this case, `a._x` will be empty." - is that really mandated anywhere? cppreference says "other is left in valid, but unspecified state." My understanding is that the string move constructor is free to "steal" any pointer, and I'd want it to, but if the string fits in a Short String Optimisation internal buffer I wouldn't want or expect the move constructor to spend time clearing out `a._x`: you explicitly don't care what's left there. – Tony Delroy Apr 10 '14 at 09:22
  • Turning C++03 copies into moves always failed the 'as-if' test: the language could have made this a move, but it was decided to restrict auto-move to cases where otherwise elision (skipping the copy) would be allowed from the source location, basically. – Yakk - Adam Nevraumont Apr 10 '14 at 13:34
5

A b = a; always invokes the copy constructor, no matter if it could invoke the move constructor. Additionally the lifetime of the object a continues after the assignment, even it is not used anymore.

If you want to use the move constructor, you have to make it explicit:

A b = std::move(a);

Note that this can be dangerous, as a is still accessible after the move. If you accidentally use it later, there may be undefined behavior.

Think about why it should happen automatically. In the example you gave, there is no need, as you can as well use a instead of b. In many cases where it would make more sense move constructor/assignment would be used automatically, e.g. A a; a = foo();.

Danvil
  • 22,240
  • 19
  • 65
  • 88
  • "If you accidentally use [a] later, there may be undefined behavior." - not generally true... the move constructor is obliged to leave it in "valid, but unspecified state". Of course, if you had a pointer/iterator to a specific character in `a` and used it later that would have undefined behaviour... the move construction is mutating and invalidates those. – Tony Delroy Apr 10 '14 at 09:25
  • Hmm... I see... I was trying to avoid cluttering my code full of std::move()-s. As far as I see right now, there is no way to avoid that. My thoughts were that the copy and the move constructors *should* perform the same operation, with the difference that the move constructor may rely on the fact that the old object will not be used any more, hence steal its pointers. With this in mind the optimizer might be able to swap copy and move constructors if it sees that the old object is not used anywhere before going out of scope, as in my case. – Ákos Vandra-Meyer Apr 10 '14 at 11:52
  • @danvil, As a reply to your "why it should happen automatically"... would it happen in case this case: ` int main() { A a("a"); A::foo(a); } ` – Ákos Vandra-Meyer Apr 10 '14 at 11:57
  • @ÁkosVandra: `void foo(A a)` would pass by value and thus use the copy constructor. `void foo(const A& a)` would pass by const reference and not use any constructor/assignment. So no move constructor/assignment here. It is not necessary as you either want a real copy (use per value) or you don't (use const reference). – Danvil Apr 10 '14 at 12:26
4

Why isn't the A b = a line optimized to use the move constructor?

Because that would change the observable behavior of the program. The compiler is not permitted to freely change the observable behavior of the program (§1.9/1), except under very specific circumstances (§12.8/31). This is not one of those circumstances. Remove the side effects from your constructors, and the compiler may optimize them away. Of course, if you remove the side effects, then you won't notice if the compiler optimizes the constructor calls away (unless you examine the assembly or binary output), but that's the whole point.

Benjamin Lindley
  • 101,917
  • 9
  • 204
  • 274