Search Knowledge

© 2026 LIBREUNI PROJECT

Modern C++ Programming / Modern C++ Features

Move Semantics and Perfect Forwarding

Move Semantics: Avoiding Expensive Copies

Before C++11, returning or passing large objects by value meant expensive copies. Move semantics allow transferring resources from temporary objects instead of copying them.

class Buffer {
    char* data_;
    size_t size_;
public:
    // Constructor
    Buffer(size_t s) : data_(new char[s]), size_(s) {}
    
    // Copy constructor (deep copy)
    Buffer(const Buffer& other) : data_(new char[other.size_]), size_(other.size_) {
        std::copy(other.data_, other.data_ + size_, data_);
    }
    
    // Move constructor (transfer ownership)
    Buffer(Buffer&& other) noexcept 
        : data_(other.data_), size_(other.size_) {
        other.data_ = nullptr;
        other.size_ = 0;
    }
    
    ~Buffer() { delete[] data_; }
};

Move Constructor and Move Assignment

Move operations should be noexcept when possible—this enables optimizations in standard containers:

class Widget {
    std::string name_;
    std::vector<int> data_;
public:
    // Move constructor
    Widget(Widget&& other) noexcept
        : name_(std::move(other.name_)),
          data_(std::move(other.data_)) {
    }
    
    // Move assignment
    Widget& operator=(Widget&& other) noexcept {
        if (this != &other) {
            name_ = std::move(other.name_);
            data_ = std::move(other.data_);
        }
        return *this;
    }
};

Note: std::move on member variables is necessary because a named rvalue reference (like other) is itself an lvalue!

std::move: Unconditional Cast to Rvalue

std::move doesn’t move anything—it just casts its argument to an rvalue reference, enabling move semantics:

std::string s1 = "hello";
std::string s2 = std::move(s1);  // s1's data moved to s2
// s1 is now in "valid but unspecified" state

After std::move: The object remains alive and destructible, but its value is unspecified. Only reassign or destroy it.

std::string s = "hello";
std::string t = std::move(s);

// s.clear();  // OK: Reset to known state
// s = "new";  // OK: Reassign
// auto len = s.size();  // DANGEROUS: Value unspecified

Move-Only Types

Some types cannot be copied, only moved: unique_ptr, thread, iostream, etc.

std::unique_ptr<int> p1 = std::make_unique<int>(42);
// std::unique_ptr<int> p2 = p1;  // Error: cannot copy
std::unique_ptr<int> p2 = std::move(p1);  // OK: move

std::vector<std::unique_ptr<int>> vec;
vec.push_back(std::make_unique<int>(10));  // Move into vector

Return Value Optimization (RVO) and NRVO

Compilers can elide copies/moves when returning by value:

Widget createWidget() {
    return Widget{};  // RVO: No move/copy, constructed directly in caller
}

Widget createWidget2() {
    Widget w;
    // ...
    return w;  // NRVO: Named Return Value Optimization (maybe)
}

Don’t std::move return values—it inhibits RVO:

Widget createWidget() {
    Widget w;
    return std::move(w);  // BAD: Prevents NRVO, forces move
}

Perfect Forwarding with std::forward

When writing wrapper functions, you want to forward arguments exactly as received—preserving lvalue/rvalue-ness. This requires forwarding references (a.k.a. universal references).

template<typename T>
void wrapper(T&& arg) {  // Forwarding reference
    target(std::forward<T>(arg));  // Perfect forwarding
}

int x = 10;
wrapper(x);        // T = int&, arg forwarded as lvalue
wrapper(10);       // T = int, arg forwarded as rvalue
wrapper(std::move(x));  // T = int, arg forwarded as rvalue

Forwarding Reference Rules

T&& is a forwarding reference only when:

  1. T is a template type parameter
  2. Type deduction occurs
template<typename T>
void func(T&& x);  // Forwarding reference

template<typename T>
void func(std::vector<T>&& x);  // NOT forwarding reference (no deduction for &&)

void func(int&& x);  // NOT forwarding reference (not a template)

Variadic Template Forwarding

Combining variadic templates with perfect forwarding:

template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args) {
    return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

auto p = make_unique<std::pair<int, std::string>>(42, "hello");

When to Use Move vs Forward

  • std::move: When you know you have an rvalue or want to treat something as rvalue
  • std::forward: In templates, when you want to preserve the value category
template<typename T>
void process(T&& arg) {
    // Use arg multiple times...
    
    target(std::forward<T>(arg));  // Forward on last use
}

Move Semantics and Exception Safety

Move operations should be noexcept when possible. Standard containers use move only if noexcept:

class Widget {
public:
    Widget(Widget&& other) noexcept { /* ... */ }
    Widget& operator=(Widget&& other) noexcept { /* ... */ }
};

std::vector<Widget> vec;
vec.push_back(Widget{});  // Uses move (noexcept)

// If move is not noexcept, vector uses copy (for strong exception guarantee)
Conceptual Check

What does std::move actually do?

Runtime Environment

Interactive Lab

1#include <iostream>
2#include <utility>
3#include <string>
4 
5class Holder {
6 std::string data_;
7public:
8 Holder(std::string s) : data_(std::move(s)) {}
9
10 Holder(const Holder&) { std::cout << "Copied\n"; }
11 Holder(Holder&&) noexcept { std::cout << "Moved\n"; }
12
13 Holder& operator=(const Holder&) { std::cout << "Copy-assigned\n"; return *this; }
14 Holder& operator=(Holder&&) noexcept { std::cout << "Move-assigned\n"; return *this; }
15};
16 
17int main() {
18 Holder h1("hello");
19 Holder h2 = h1; // Copy
20 Holder h3 = std::move(h1); // Move
21 h2 = h3; // Copy assign
22 h2 = std::move(h3); // Move assign
23 return 0;
24}
System Console

Waiting for signal...