Skip to content

Move_semantics

Move semantics is one of the most important features of modern C++. It enables efficient resource transfer without copying.

int x = 10; // x is lvalue, 10 is rvalue
// Lvalue: has address, can appear on left of =
// Rvalue: temporary, can only appear on right of =
int x = 10;
int&& rref = 10; // Rvalue reference to temporary
// int&& rref2 = x; // Error: can't bind rvalue ref to lvalue
// Lvalue reference
int& lref = x; // Lvalue reference to x
std::vector<int> createVector() {
std::vector<int> v = {1, 2, 3, 4, 5};
return v; // Returns by value - copies the vector!
}

Before C++11, returning a large container would copy it.

class Buffer {
private:
int* data;
size_t size;
public:
Buffer(size_t s) : size(s) {
data = new int[size];
}
// Copy constructor
Buffer(const Buffer& other) : size(other.size) {
data = new int[size];
std::copy(other.data, other.data + size, data);
}
// Move constructor
Buffer(Buffer&& other) noexcept : data(other.data), size(other.size) {
other.data = nullptr; // Transfer ownership
other.size = 0;
}
~Buffer() { delete[] data; }
};
Buffer createBuffer() {
Buffer b(1000);
return b; // Now moves instead of copies!
}
int main() {
Buffer b1(1000);
Buffer b2(std::move(b1)); // Move constructor called
// b1 is now in valid but unspecified state
}

std::move converts an lvalue to rvalue:

std::vector<int> v1 = {1, 2, 3};
std::vector<int> v2 = std::move(v1); // Move, not copy
// v1 is now empty (valid but unspecified)
  1. Returning local objects
std::vector<int> getData() {
std::vector<int> data = {1, 2, 3};
return data; // RVO or move
}
  1. Moving into containers
std::vector<std::vector<int>> vec;
vec.push_back(std::move(myVector));
  1. Moving members in constructors
class MyClass {
std::vector<int> data;
public:
MyClass(std::vector<int> d) : data(std::move(d)) {}
};
class Buffer {
// ... (same members as before)
// Move assignment operator
Buffer& operator=(Buffer&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data;
size = other.size;
other.data = nullptr;
other.size = 0;
}
return *this;
}
};
int main() {
Buffer b1(100);
Buffer b2(200);
b2 = std::move(b1); // Move assignment
}

If you define any of these, define all five:

class MyClass {
private:
int* data;
public:
// 1. Destructor
~MyClass() { delete[] data; }
// 2. Copy constructor
MyClass(const MyClass& other);
// 3. Copy assignment
MyClass& operator=(const MyClass& other);
// 4. Move constructor
MyClass(MyClass&& other) noexcept;
// 5. Move assignment
MyClass& operator=(MyClass&& other) noexcept;
};
#include <iostream>
#include <vector>
#include <string>
class BigObject {
private:
std::vector<int> data;
std::string name;
public:
BigObject(const std::string& n, size_t s) : name(n) {
data.resize(s, 0);
std::cout << "Constructing " << name << "\n";
}
// Copy constructor
BigObject(const BigObject& other)
: name(other.name + "_copy"), data(other.data) {
std::cout << "Copying " << name << "\n";
}
// Move constructor
BigObject(BigObject&& other) noexcept
: name(std::move(other.name)), data(std::move(other.data)) {
std::cout << "Moving " << name << "\n";
}
void print() const {
std::cout << name << " with " << data.size() << " elements\n";
}
};
int main() {
std::cout << "=== Creating obj1 ===\n";
BigObject obj1("first", 1000);
std::cout << "\n=== Copying obj1 to obj2 ===\n";
BigObject obj2 = obj1; // Copy
std::cout << "\n=== Moving obj1 to obj3 ===\n";
BigObject obj3 = std::move(obj1); // Move
std::cout << "\n=== Vector operations ===\n";
std::vector<BigObject> vec;
vec.push_back(BigObject("temp", 500));
std::cout << "\n=== End ===\n";
return 0;
}
  • Move semantics transfer ownership without copying
  • Rvalue references (&&) bind to temporaries
  • std::move converts lvalue to rvalue
  • Move constructors transfer resources efficiently
  • Follow the Rule of Five when managing resources

Now let’s continue with more modern C++ features.

Next Chapter: 27_nullptr.md - nullptr and Modern Type Safety