When Does C++ Call the Move Constructor?
Sep 04, 2025This 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