SyntaxStudy
Sign Up
C++ Move Semantics and the Rule of Five
C++ Beginner 1 min read

Move Semantics and the Rule of Five

C++11 introduced move semantics to eliminate unnecessary deep copies when an object is a temporary (rvalue) or is explicitly moved. A move constructor transfers ownership of resources from the source object, leaving it in a valid but unspecified state. This avoids costly allocations that would otherwise occur when returning large objects from functions or inserting them into containers. The Rule of Five extends the Rule of Three: any class that manages resources should define all five special member functions — destructor, copy constructor, copy-assignment operator, move constructor, and move-assignment operator. Failing to define a move constructor forces the compiler to fall back to copy, negating the performance benefit of move-aware containers and algorithms. 'std::move' is a cast that converts an lvalue into an rvalue reference, enabling the move overloads to be selected. It does not actually move anything — the move happens in the constructor or assignment operator that receives the rvalue reference. After a move, the source object should be left in a state from which it can be safely destroyed or reassigned.
Example
#include <iostream>
#include <utility>   // std::move, std::exchange

class Buffer {
public:
    explicit Buffer(std::size_t size)
        : data_(new int[size]), size_(size) {
        std::cout << "Constructed buffer[" << size_ << "]\n";
    }

    // Destructor
    ~Buffer() {
        delete[] data_;
        std::cout << "Destroyed buffer[" << size_ << "]\n";
    }

    // Copy constructor (deep copy)
    Buffer(const Buffer& other)
        : data_(new int[other.size_]), size_(other.size_) {
        std::copy(other.data_, other.data_ + size_, data_);
        std::cout << "Copied buffer[" << size_ << "]\n";
    }

    // Copy-assignment
    Buffer& operator=(const Buffer& other) {
        if (this == &other) return *this;
        delete[] data_;
        size_ = other.size_;
        data_ = new int[size_];
        std::copy(other.data_, other.data_ + size_, data_);
        return *this;
    }

    // Move constructor (steals resources)
    Buffer(Buffer&& other) noexcept
        : data_(std::exchange(other.data_, nullptr))
        , size_(std::exchange(other.size_, 0)) {
        std::cout << "Moved buffer[" << size_ << "]\n";
    }

    // Move-assignment
    Buffer& operator=(Buffer&& other) noexcept {
        if (this == &other) return *this;
        delete[] data_;
        data_ = std::exchange(other.data_, nullptr);
        size_ = std::exchange(other.size_, 0);
        return *this;
    }

    std::size_t size() const { return size_; }

private:
    int*        data_;
    std::size_t size_;
};

int main() {
    Buffer a(8);
    Buffer b = std::move(a);      // move constructor
    Buffer c(4);
    c = std::move(b);             // move-assignment
    std::cout << "c.size() = " << c.size() << "\n";
    return 0;
}