Move Semantics in C++
Move semantics is a feature that allows our program to transfer ownership of resources (like memory, files, etc.) from one object to another instead of copying them. This results iin:
- Faster performance
- Less memory usage
- Better efficiency, especially with big objects (like std::vector, std::string, or file streams)
Why use Move Semantics?
Normally when we copy an object that owns a resource, both objects must mange separate copies of that resource.
But copying is expensive - especially for big data.
With move semantics we can transfer the resource from one object to another, leaving the first object in the "moved-from" state which means it's still a valid object but it no longer owns the original resource (like text in a string or data in a vector).
Example:
#include <iostream>
#include <string>
int main() {
std::string original = "Hello, World!";
// Copying the string
std::string copy = original;
// Moving the string
std::string moved = std::move(original);
// Output all three
std::cout << "Original: '" << original << "'" << std::endl;
std::cout << "Copy: '" << copy << "'" << std::endl;
std::cout << "Moved: '" << moved << "'" << std::endl;
return 0;
}
Output
Original: '' Copy: 'Hello, World!' Moved: 'Hello, World!'
Copy vs Move
Feature | Copy | Move |
---|---|---|
What it does | Creates a full duplicate | Transfers ownership |
Speed | Slower (copies memory/resources) | Faster (just move pointers) |
Old object | Still holds a valid copy | Becomes empty or "moved-from" |
When used | When original is still needed | When original is temporary/disposable |
Code Example | std::string b=a; | std::string b=std::move(a); |
Types of Expressions in C++
To understand move semantics, we need to know what lvalues and rvalues are:
- Lvalue Reference (T&) : An lvalue reference is a reference that refers to an existing object with a name and a stable location in memory.
// x is an lvalue
int x = 10;
// ref is an lvalue reference to x
int& ref = x;
// changes x to 20
ref = 20;
- Rvalue Reference (T&&) : An rvalue reference is a reference that can bind to temporary objects or values that are about to be destroyed (like the result of expressions or temporary variables).
// 5 is an rvalue (temporary)
int&& rref = 5;
int x = 10;
// x + 2 is a temporary rvalue
int&& rref2 = x + 2;
Why Move Semantics Only Apply to Rvalues?
Move Semantics is about transferring ownership of resources, we can only do that safely if we know no one else needs it anymore.
That's why :
- We can move from rvalues - because they are temporary and about to disappear anyway.
- We should not move from lvalues - because they might still be used later.
Differences Between Lvalue Reference and Rvalue Reference
Feature | Lvalue Reference (T&) | Rvalue Reference (T&&) |
---|---|---|
Binds to | Named objects (lvalues) | Temporary objects (rvalues) |
Can Appear On | Left or right side of assignment | Only on right side (usually temporary) |
Syntax | Single ampersand & | Double ampersand && |
Usage | To refer to existing variables | To enable move semantics and bind temporaries |
Modifies | The original named object | Can "steal" resources from temporaries. |
Common Use Case | Passing objects by reference to avoid copying. | Implementing move constructors and move assignments. |
Move Semantics in STL Containers
One of the most practical and important uses of move semantics is in standard library containers like std::vector, std:: map, std:: set and others. These containers use move operations internally to improve performance and avoid unnecessary copying.
STL containers like std::vector use move semantics to improve performance in two main cases:
1. During Reallocation
When a container grows and needs more space, it reallocates memory transfers existing elements.
If the element type supports move, the container moves elements instead of copying them - making reallocation faster.
2. When Inserting Elements
Move semantics are also used when inserting elements:
- push_back(std::move(x)) - Moves an existing object into the container.
- emplace_back(args...) - Constructs the object directly in-place, avoiding copies or moves.
Example:
std::string temp = "Alice";
// Moves temp into vector
names.push_back(std::move(temp));
// Constructs "Bob" directly in vector
names.emplace_back("Bob");
These operations reduce unnecessary copying and make insertion more efficient.