When Does Move Constructor Get Called

When Does Move Constructor get called?

A move constructor is called:

  • when an object initializer is std::move(something)
  • when an object initializer is std::forward<T>(something) and T is not an lvalue reference type (useful in template programming for "perfect forwarding")
  • when an object initializer is a temporary and the compiler doesn't eliminate the copy/move entirely
  • when returning a function-local class object by value and the compiler doesn't eliminate the copy/move entirely
  • when throwing a function-local class object and the compiler doesn't eliminate the copy/move entirely

This is not a complete list. Note that an "object initializer" can be a function argument, if the parameter has a class type (not reference).

a RetByValue() {
a obj;
return obj; // Might call move ctor, or no ctor.
}

void TakeByValue(a);

int main() {
a a1;
a a2 = a1; // copy ctor
a a3 = std::move(a1); // move ctor

TakeByValue(std::move(a2)); // Might call move ctor, or no ctor.

a a4 = RetByValue(); // Might call move ctor, or no ctor.

a1 = RetByValue(); // Calls move assignment, a::operator=(a&&)
}

When is a move constructor called in practice?

In your examples the moved from objects are temporaries, but thats not always the case when moving. Sometimes we know that we can move because the moved from object will not be used anymore even though it is not a temporary. Consider this type:

struct foo {
foo() = default;
foo(foo&& f) noexcept {
std::cout << "move\n";
}
};

When you create a vector of foos and the vector reallocates it will not copy the elements, but it will move them. For example:

#include <iostream>
#include <vector>

int main() {
std::vector<foo> v;
v.resize(5);
v.resize(v.capacity()+1); // force the vector to reallocate
}

output:

move
move
move
move
move

There is no way the copy or move could have been elided, because the elements are in the old place and have to get to the new place in memory somehow.

Confused when move constructor is called?

VS2012 has a nasty extension that allows temporaries to bind to lvalue-references. We can't reproduce with modern, standard-conforming compilers, which in fact elide the call to the copy/move-constructor (because of RVO). Applying the option fno-elide-constructors shows that they do indeed call the move-constructor even with the absence of const.

Why is the copy constructor called instead of the move constructor when returning?

Now I am returning this class via lvalue like this:

MyClass func()
{
return MyClass();
}

No, the returned expression is an xvalue (a kind of rvalue), used to initialise the result for return-by-value (things are a little more complicated since C++17, but this is still the gist of it; besides, you're on C++11).

In this case the move constructor gets called when returning the class object and everything works as expected.

Indeed; an rvalue will initialise an rvalue reference and thus the whole thing can match move constructors.

When I change the code above:

… now the expression is MyClass() << 5, which has type MyClass&. This is never an rvalue. It's an lvalue. It's an expression that refers to an existing object.

So, without an explicit std::move, that'll be used to copy-initialise the result. And, since your copy constructor is deleted, that can't work.


I'm surprised the example compiles at all, since a temporary can't be used to initialise an lvalue reference (your operator's first argument), though some toolchains (MSVS) are known to accept this as an extension.


then would return std::move(MyClass() << 5); work?

Yes, I believe so.

However that is very strange to look at, and makes the reader double-check to ensure there are no dangling references. This suggests there's a better way to accomplish this that results in clearer code:

MyClass func()
{
MyClass m;
m << 5;
return m;
}

Now you're still getting a move (because that's a special rule when returning local variables) without any strange antics. And, as a bonus, the << call is completely standard-compliant.

When move constructor are called

A move constructor is just another constructor. If you have overloaded constructors, just like having any other functions overloaded, the choice of which constructor is called comes down to the rules of overload resolution. That is, when you construct an object with Foo a(<some-expression>);, there might be multiple possible constructors and one needs to be chosen.

The copy constructor takes one argument of type const Foo&. This lvalue reference type will bind to any expression denoting a Foo object. The move constructor takes one argument of type Foo&&. This rvalue reference will only bind to modifiable rvalues. In fact, this overload will be preferred in the case of passing a modifiable rvalue.

This means that in Foo a(<some-expression>); if the expression <some-expression> is a modifiable rvalue, the move constructor will be chosen. Otherwise the copy constructor is chosen. Modifiable rvalues usually appear when denoting temporary objects (for example, an object returned from a function). It is also possible to force an expression to an rvalue expression by using std::move, such as Foo a(std::move(b));.

Why isn't the move constructor being called?

Why doesn't Class obj2(obj1.fun()); call the move constructor

Because of Copy Elison. The compiler sees that fun() returns a temporary object, and that temporary will only be used to initialize obj2, so the compiler optimizes the creation of obj2 by eliminating the temporary object altogether and allowing obj2 to be created directly inside of fun() itself, thus there is no need for a copy/move operation when fun() exits.

Class obj2(std::move(obj1.fun())); does call the move constructor

Because you are forcing it with the explicit std::move type-cast, so the compiler can't optimize the creation of obj2 via Copy Elison, so it has to allow fun() to return a temporary object, which you are then moving into the obj2 constructor.

obj3 = obj2.fun() calls the (move) assignment operator

Because obj3 already exists before the assignment.

without needing to write std::move(obj2.fun())

Because fun() returns a temporary object, which is an rvalue, so there is no need to explicitly type-cast it to an rvalue when calling a move assignment operator.

move constructor called on return instead of copy

From cppreference (emphasis mine):

If [the returned expression] is an lvalue expression and the conditions for copy elision are met, or would be met, except that [the returned expression] names a function parameter, then overload resolution to select the constructor to use for initialization of the returned value is performed twice: first as if [the returned expression] were an rvalue expression (thus it may select the move constructor or a copy constructor taking reference to const), and if no suitable conversion is available, overload resolution is performed the second time, with lvalue [returned expression] (so it may select the copy constructor taking a reference to non-const).
The above rule applies even if the function return type is different from the type of [the returned expression] (copy elision requires same type)

Long story short, return implicitly tries to move what you return when it makes sense. It will only copy as a last resort (no move constructor available, for example).



Related Topics



Leave a reply



Submit