A move constructor is a special constructor that transfers the ownership of resources (such as dynamically allocated memory, file handles, or sockets) from one object to another instead of copying them.
- Improves performance by avoiding unnecessary deep copies.
- Mainly used with temporary objects (rvalues).
#include <iostream>
using namespace std;
class Geeks {
private:
int* ptr;
public:
// Constructor
Geeks(int value) {
// Dynamically allocate memory
ptr = new int(value);
cout << "Constructor called\n";
}
// Move Constructor
Geeks(Geeks&& obj) {
cout << "Move Constructor called\n";
// Steal the pointer
ptr = obj.ptr;
obj.ptr = nullptr;
}
// Destructor
~Geeks() {
if (ptr != nullptr) {
cout << "Destructor deleting data: " << *ptr << endl;
} else {
cout << "Destructor called on nullptr\n";
}
delete ptr;
}
// Display function
void display() {
if (ptr)
cout << "Value: " << *ptr << endl;
else
cout << "No data\n";
}
};
int main() {
// Constructor is called
Geeks obj1(42);
// Move constructor is called
Geeks obj2 = std::move(obj1);
cout << "\nAfter move:\n";
cout << "obj1: ";
// Should show "No data"
obj1.display();
cout << "obj2: ";
// Should show "Value: 42"
obj2.display();
return 0;
}
Output
Constructor called Move Constructor called After move: obj1: No data obj2: Value: 42 Destructor deleting data: 42 Destructor called on nullptr
Syntax
ClassName(ClassName&& other);
Where,
- ClassName&& is an rvalue reference.
- other is the temporary object whose resources are transferred.
- The source object is usually left in a valid but empty state (commonly by setting pointers to nullptr).
Situations Where Move Constructors Are Invoked
A move constructor is typically invoked in the following situations:
- Returning an object by value.
- Initializing an object from a temporary object.
- Using std::move() on an existing object.
- During container reallocations (for example, std::vector) when move operations are available.
Example:
MyClass obj2 = std::move(obj1);
Move Constructor in STL Containers
STL containers frequently relocate their elements while resizing. A move constructor significantly reduces the overhead of these operations.
Without Move Constructor
When a move constructor is not available, the compiler uses the copy constructor during container reallocation.
- Every element is copied.
- Deep copies are performed repeatedly.
- More constructor and destructor calls occur.
- Results in lower performance.
#include <iostream>
#include <vector>
using namespace std;
// Move Class
class Move {
private:
// Declaring the raw pointer as
// the data member of the class
int* data;
public:
// Constructor
Move(int d)
{
// Declare object in the heap
data = new int;
*data = d;
cout << "Constructor is called for " << d << endl;
};
// Copy Constructor to delegated
// Copy constructor
Move(const Move& source)
: Move{ *source.data }
{
// Copying constructor copying
// the data by making deep copy
cout << "Copy Constructor is called - "
<< "Deep copy for " << *source.data << endl;
}
// Destructor
~Move()
{
if (data != nullptr)
// If the pointer is not
// pointing to nullptr
cout << "Destructor is called for " << *data
<< endl;
else
// If the pointer is
// pointing to nullptr
cout << "Destructor is called"
<< " for nullptr" << endl;
// Free the memory assigned to
// data member of the object
delete data;
}
};
// Driver Code
int main()
{
// Create vector of Move Class
vector<Move> vec;
// Inserting object of Move class
vec.push_back(Move{ 10 });
vec.push_back(Move{ 20 });
return 0;
}
Output
Constructor is called for 10 Constructor is called for 10 Copy Constructor is called - Deep copy for 10 Destructor is called for 10 Constructor is called for 20 Constructor is called for 20 Copy Constructor is called - Deep copy for 20 Constructor is called for 10 Copy Constructor is called - Deep copy for 10 Destructor is called for 10 Destructor is called for 20 Destructor is called for 10 Destructor is called for 20
Explanation
- Since the class provides only a copy constructor, existing elements are deep-copied whenever the vector grows.
- These additional copy operations increase the overall execution time.
With Move Constructor
Adding a move constructor allows the container to transfer resources instead of copying them.
- Existing resources are reused.
- Fewer deep copies are required.
- Performance improves significantly.
- Temporary objects become inexpensive to relocate.
#include <iostream>
#include <vector>
using namespace std;
// Move Class
class Move {
private:
// Declare the raw pointer as
// the data member of class
int* data;
public:
// Constructor
Move(int d)
{
// Declare object in the heap
data = new int;
*data = d;
cout << "Constructor is called for " << d << endl;
};
// Copy Constructor
Move(const Move& source)
: Move{ *source.data }
{
// Copying the data by making
// deep copy
cout << "Copy Constructor is called -"
<< "Deep copy for " << *source.data << endl;
}
// Move Constructor
Move(Move&& source)
: data{ source.data }
{
cout << "Move Constructor for " << *source.data
<< endl;
source.data = nullptr;
}
// Destructor
~Move()
{
if (data != nullptr)
// If pointer is not pointing
// to nullptr
cout << "Destructor is called for " << *data
<< endl;
else
// If pointer is pointing
// to nullptr
cout << "Destructor is called"
<< " for nullptr " << endl;
// Free up the memory assigned to
// The data member of the object
delete data;
}
};
// Driver Code
int main()
{
// Vector of Move Class
vector<Move> vec;
// Inserting Object of Move Class
vec.push_back(Move{ 10 });
vec.push_back(Move{ 20 });
return 0;
}
Output
Constructor is called for 10 Move Constructor for 10 Destructor is called for nullptr Constructor is called for 20 Move Constructor for 20 Constructor is called for 10 Copy Constructor is called -Deep copy for 10 Destructor is called for 10 Destructor is called for nullptr Destructor is called for 10 Destructor is called for 20
Explanation
- Temporary objects are moved instead of copied, and ownership of dynamically allocated memory is transferred.
- No additional memory allocation is required, which improves performance by avoiding expensive deep-copy operations.
Note: During vector reallocation, some copy constructor calls may still occur if move operations are not guaranteed to be safe. This is where noexcept becomes important.
noexcept Move Constructor
A move constructor can be marked with the noexcept specifier to indicate that it will never throw an exception.
- Guarantees that moving objects is exception-safe.
- Allows STL containers to use move operations more aggressively.
- Improves container performance during resizing.
Without noexcept
If a move constructor is not marked noexcept, STL containers may prefer copying objects to ensure exception safety.
#include <bits/stdc++.h>
using namespace std;
class A {
public:
A() {}
// Move constructor not marked as noexcept
A(A&& other) {
cout << "Move constructor" << endl;
}
// Copy constructor
A(const A& other) {
cout << "Copy constructor" << endl;
}
};
int main() {
vector<A> v;
// Reserve space for at least two elements to
// avoid immediate resizing
v.reserve(2);
// Uses the move constructor for the temporary objects
v.push_back(A());
v.push_back(A());
cout << "Resize happens" << endl;
// Move constructor may be called again if resizing occurs
v.push_back(A());
return 0;
}
Output
Move constructor Move constructor Resize happens Move constructor Copy constructor Copy constructor
Explanation: Since the move constructor is not marked noexcept, std::vector cannot guarantee exception safety during reallocation and therefore prefers copying existing elements instead of moving them.
With noexcept
Marking the move constructor as noexcept enables the STL to move existing elements during reallocation.
#include <vector>
#include <iostream>
using namespace std;
class Test {
public:
Test(){}
// Move constructor marked as noexcept
Test(Test&& other) noexcept {
cout << "Move constructor " << endl;
}
// Copy constructor
Test(const Test& other) {
cout << "Copy constructor " << endl;
}
};
int main() {
vector<Test> vec;
vec.reserve(2); // Reserve space for at least two elements
Test a;
vec.push_back(Test());
vec.push_back(Test()); // Uses the move constructor
cout << "Resize happens" << endl;
vec.push_back(Test());
return 0;
}
Output
Move constructor Move constructor Resize happens Move constructor Move constructor Move constructor
Explanation: Since the move constructor is declared noexcept, std::vector can safely move elements instead of copying them during reallocation, making the operation more efficient.
Benefits of Using noexcept
Marking a move constructor as noexcept enables safer and more efficient optimizations.
- Allows STL containers to move elements during reallocation.
- Reduces unnecessary copy operations.
- Enables better compiler optimizations.
- Recommended for move constructors that cannot throw exceptions.
Copy Constructor vs Move Constructor
| Feature | Copy Constructor | Move Constructor |
|---|---|---|
| Purpose | Creates a copy of an object's resources | Transfers ownership of resources |
| Parameter | const ClassName& | ClassName&& |
| Resource Handling | Duplicates resources | Transfers resources |
| Memory Allocation | Usually allocates new memory | No new allocation in most cases |
| Performance | Slower | Faster |
| Source Object | Remains unchanged | Left in a valid but unspecified state (commonly nullptr) |
| Introduced | C++98 | C++11change |
Advantages of Move Constructors
Move constructors improve performance by transferring ownership of resources instead of copying them.
- Avoid expensive deep-copy operations.
- Reduce unnecessary memory allocations.
- Improve the performance of STL containers during reallocation.
- Efficiently manage dynamic resources such as memory and file handles.