Move Semantics & Rvalue References
Efficient resource transfer in modern C++
🚀 What are Move Semantics?
Move semantics allow efficient transfer of resources from temporary objects using rvalue references (&&), avoiding expensive copying operations and improving performance significantly.
std::vector<int> createVector() {
return std::vector<int>{1, 2, 3, 4, 5}; // Returned by move
}
std::vector<int> vec = createVector(); // Move, not copy!
Result:
Efficient transfer without copying data
Move Semantics Concepts
Lvalue vs Rvalue
Named objects vs temporary values
int x = 5; // x is lvalue, 5 is rvalue
Rvalue References
Bind to temporary objects
int&& rref = std::move(x);
Move Constructor
Transfer resources efficiently
MyClass(MyClass&& other) noexcept
std::move
Cast lvalue to rvalue reference
auto moved = std::move(object);
🔹 Basic Move Example
Move semantics transfer resources from one object to another, avoiding expensive copies. An rvalue
reference (&&) binds to temporary objects, enabling the move constructor or assignment operator to "steal" the
internal data (like a dynamic array) from the source, leaving it in a valid but empty state. This is critical for
performance when dealing with heavy objects like strings or containers. Understanding the difference between lvalues
(named, persistent) and rvalues (temporary) is key to using std::move effectively.
#include <iostream>
#include <string>
#include <utility>
void processString(const std::string& s) {
std::cout << "Lvalue reference: " << s << std::endl;
}
void processString(std::string&& s) {
std::cout << "Rvalue reference: " << s << std::endl;
}
int main() {
std::string name = "Alice";
// Lvalue - named object
processString(name);
// Rvalue - temporary object
processString("Bob");
processString(std::string("Charlie"));
// Convert lvalue to rvalue using std::move
processString(std::move(name));
// name is now in "moved-from" state (valid but unspecified)
std::cout << "name after move: '" << name << "'" << std::endl;
return 0;
}
Output:
Lvalue reference: Alice
Rvalue reference: Bob
Rvalue reference: Charlie
Rvalue reference: Alice
name after move: ''
🔹 Custom Move Constructor
Implementing move constructors and assignment optimizes resource management in custom classes. The move constructor typically takes an rvalue reference to another instance, transfers its pointers (setting the source's pointers to null), and copies any other trivial members. This leaves the source in a destructible state. Properly implemented move semantics can make operations like returning large objects from functions by value surprisingly efficient, as it often becomes a simple pointer swap instead of a deep copy.
#include <iostream>
#include <cstring>
class MyString {
private:
char* data;
size_t length;
public:
// Constructor
MyString(const char* str) {
length = strlen(str);
data = new char[length + 1];
strcpy(data, str);
std::cout << "Constructor: " << data << std::endl;
}
// Copy constructor (expensive)
MyString(const MyString& other) {
length = other.length;
data = new char[length + 1];
strcpy(data, other.data);
std::cout << "Copy constructor: " << data << std::endl;
}
// Move constructor (efficient)
MyString(MyString&& other) noexcept {
data = other.data;
length = other.length;
other.data = nullptr;
other.length = 0;
std::cout << "Move constructor: " << data << std::endl;
}
// Destructor
~MyString() {
if (data) {
std::cout << "Destructor: " << data << std::endl;
delete[] data;
}
}
const char* c_str() const { return data ? data : ""; }
};
MyString createString() {
return MyString("Temporary String"); // Move on return
}
int main() {
std::cout << "=== Creating temporary ===" << std::endl;
MyString str1 = createString(); // Move constructor called
std::cout << "\n=== Using std::move ===" << std::endl;
MyString str2 = std::move(str1); // Move constructor called
std::cout << "\nstr1: " << str1.c_str() << std::endl;
std::cout << "str2: " << str2.c_str() << std::endl;
return 0;
}
Output:
=== Creating temporary ===
Constructor: Temporary String
Move constructor: Temporary String
=== Using std::move ===
Move constructor: Temporary String
str1:
str2: Temporary String
Destructor: Temporary String
🔹 Perfect Forwarding
Perfect forwarding preserves the value category (lvalue/rvalue) of function arguments. Using a
universal reference (template typename T&&) and std::forward<T>, a function
template can pass arguments to another function exactly as they were received. This avoids unnecessary copies when
forwarding temporaries and maintains the ability to modify lvalues. It's the cornerstone of factory functions,
emplacement in containers, and wrapper functions that need to transparently pass arguments through multiple layers.
#include <iostream>
#include <utility>
#include <string>
class Logger {
public:
void log(const std::string& message) {
std::cout << "Logging lvalue: " << message << std::endl;
}
void log(std::string&& message) {
std::cout << "Logging rvalue: " << message << std::endl;
}
};
// Perfect forwarding function template
template<typename T>
void forwardToLogger(Logger& logger, T&& message) {
logger.log(std::forward<T>(message));
}
int main() {
Logger logger;
std::string msg = "Hello World";
// Forward lvalue
forwardToLogger(logger, msg);
// Forward rvalue
forwardToLogger(logger, "Temporary Message");
forwardToLogger(logger, std::move(msg));
return 0;
}
Output:
Logging lvalue: Hello World
Logging rvalue: Temporary Message
Logging rvalue: Hello World