When Does C++ Call the Move Constructor?

Sep 04, 2025

This is a note to myself on some of the occasions the move constructor is called in C++. Let’s use a simple class that implements move constructor and move assignment with some print statements for debugging/confirmation.

class A {
public:
  A() { std::cout << "Default constructor called\n"; }

  A(A &&other) noexcept { std::cout << "Move constructor called\n"; }

  auto operator=(A &&other) noexcept -> A & {
    std::cout << "Move assignment called\n";
    return *this;
  }
};

I explore 5 cases, which I think cover most of the situations. I’d love to know if there are more.

1st Case - Explicit

The move constructor is called when we explicitly pass an rvalue to the class constructor:

A a; // Default constructor called
A a2(std::move(a)); // Move constructor called

2nd Case - Initializing from a temporary object

When we initialize a variable from an rvalue, the move constructor is called.

auto a3 = std::move(a2); // Move constructor called

Note that assigning to a variable already initialized does not call the move constructor, but move assignment instead:

A a4;
a4 = std::move(a3); // Move assignment called

3rd Case - Returning a local object from a function

This is an interesting one. When we have a function that returns a local object, for example:

A make_a() {
  A a;
  return a;
}

The move constructor can or cannot be called depending on whether the Return Value Optimization is applied or not. For this case, in C++17, the move constructor will not be called because the optimization is applied. We can disable RVO with the flag -fno-elide-constructors, and see the move constructor being called.

4th Case - Passing rvalue to a function

Here we have a function that receives A by value, meaning a new object A must be constructed. However, if you pass an rvalue to that function, you get a move constructor call.

void receive_a(A a) {}
receive_a(std::move(a5)); // Move constructor called

Think of this as the same as 2nd Case:

A a = std::move(a5);

Think about what happens in the following cases:

void receive_a(A&& a) {}

void receive_a(A&& a) {
    A new_a = std::move(a);
}

5th Case: Emplacing into standard containers

Standard containers usually implement move semantics and take ownership when an rvalue is passed:

std::vector<A> v;
A a6;
v.push_back(std::move(a6)); // Move constructor called

#c++ #move semantics #move constructor