std::swap vs std::exchange vs swap operator

Learn std::swap vs std::exchange vs swap operator with practical examples, diagrams, and best practices. Covers c++, swap, c++14 development techniques with visual explanations.

Mastering Value Swapping in C++: std::swap vs. std::exchange vs. Custom Swap

Hero image for std::swap vs std::exchange vs swap operator

Explore the nuances of value swapping in C++11/14 and beyond, comparing std::swap, std::exchange, and custom swap operators to optimize performance and ensure correctness.

Swapping the values of two variables is a fundamental operation in programming. In C++, there are several ways to achieve this, each with its own use cases, advantages, and potential pitfalls. This article delves into the standard library functions std::swap and std::exchange (introduced in C++14), as well as the concept of providing a custom swap operator for user-defined types. Understanding their differences is crucial for writing efficient, correct, and idiomatic C++ code.

std::swap: The General-Purpose Swapper

std::swap is the most common and versatile way to exchange the values of two objects. It's defined in the <utility> header (or <algorithm> for older C++ versions) and typically implemented using move semantics for efficiency. For fundamental types, it performs a simple three-assignment swap using a temporary variable. For user-defined types, it will either use a specialized std::swap overload or, by default, fall back to move-constructing a temporary and then move-assigning. This behavior makes it robust for a wide range of types.

#include <iostream>
#include <utility> // For std::swap
#include <vector>

int main() {
    int a = 10;
    int b = 20;
    std::cout << "Before std::swap: a = " << a << ", b = " << b << std::endl;
    std::swap(a, b);
    std::cout << "After std::swap:  a = " << a << ", b = " << b << std::endl;

    std::vector<int> v1 = {1, 2, 3};
    std::vector<int> v2 = {4, 5, 6, 7};
    std::cout << "\nBefore std::swap: v1 size = " << v1.size() << ", v2 size = " << v2.size() << std::endl;
    std::swap(v1, v2);
    std::cout << "After std::swap:  v1 size = " << v1.size() << ", v2 size = " << v2.size() << std::endl;

    return 0;
}

Basic usage of std::swap with integral types and std::vector.

std::exchange: Swapping and Returning the Old Value

Introduced in C++14, std::exchange (from <utility>) serves a slightly different, more specific purpose than std::swap. It assigns a new value to an object and returns the object's old value. This is particularly useful in scenarios where you need to retrieve the current state of an object before modifying it, often seen in move constructors, assignment operators, or state-management functions.

#include <iostream>
#include <utility> // For std::exchange

class MyResource {
public:
    int value;
    MyResource(int v = 0) : value(v) { std::cout << "MyResource(" << value << ") constructed\n"; }
    MyResource(MyResource&& other) noexcept : value(std::exchange(other.value, 0)) {
        std::cout << "MyResource move constructed from " << other.value << " (old value) to " << value << " (new value)\n";
    }
    ~MyResource() { std::cout << "MyResource(" << value << ") destructed\n"; }
};

int main() {
    int current_state = 100;
    int new_state = 200;

    // Assign new_state to current_state, and get the old value of current_state
    int old_state = std::exchange(current_state, new_state);

    std::cout << "\nstd::exchange example:\n";
    std::cout << "Old state: " << old_state << std::endl; // 100
    std::cout << "Current state: " << current_state << std::endl; // 200

    std::cout << "\nMyResource move constructor example:\n";
    MyResource r1(50);
    MyResource r2 = std::move(r1); // Uses move constructor with std::exchange
    std::cout << "r1.value after move: " << r1.value << std::endl; // 0
    std::cout << "r2.value after move: " << r2.value << std::endl; // 50

    return 0;
}

Demonstrating std::exchange for value replacement and in a move constructor.

flowchart TD
    A[Initial State] --> B{Call std::exchange(obj, new_val)}
    B --> C[Store obj's current value in temp]
    C --> D[Assign new_val to obj]
    D --> E[Return temp (obj's old value)]
    E --> F[Final State]

Process flow of std::exchange.

Custom Swap Operators: Optimizing for User-Defined Types

For complex user-defined types, especially those managing resources (like dynamically allocated memory or file handles), the default std::swap (which relies on move construction and assignment) can be inefficient. A custom swap operator can often perform a more efficient exchange by directly swapping the internal resources or pointers, avoiding unnecessary deep copies or allocations. This is a common idiom in C++ for implementing the copy-and-swap idiom for strong exception safety.

#include <iostream>
#include <utility> // For std::swap
#include <vector>

class MyVectorWrapper {
private:
    std::vector<int> data;
public:
    MyVectorWrapper(std::initializer_list<int> il) : data(il) {
        std::cout << "MyVectorWrapper constructed with size " << data.size() << "\n";
    }

    // Custom non-member swap function (friendship is optional but common for access)
    friend void swap(MyVectorWrapper& first, MyVectorWrapper& second) noexcept {
        using std::swap; // Enable ADL
        swap(first.data, second.data); // Swaps internal std::vector objects efficiently
        std::cout << "Custom swap called!\n";
    }

    void print_size() const {
        std::cout << "Size: " << data.size() << "\n";
    }
};

int main() {
    MyVectorWrapper mv1 = {1, 2, 3};
    MyVectorWrapper mv2 = {4, 5, 6, 7, 8};

    std::cout << "\nBefore swap:\n";
    mv1.print_size(); // Size: 3
    mv2.print_size(); // Size: 5

    // Call std::swap, which will find our custom swap via ADL
    using std::swap; // Bring std::swap into scope
    swap(mv1, mv2);

    std::cout << "\nAfter swap:\n";
    mv1.print_size(); // Size: 5
    mv2.print_size(); // Size: 3

    return 0;
}

Implementing a custom swap for a user-defined type to efficiently exchange internal resources.

classDiagram
    class MyVectorWrapper {
        -std::vector<int> data
        +MyVectorWrapper(initializer_list<int>)
        +print_size() const
    }
    class std::vector<int> {
        // ... internal vector members
    }
    MyVectorWrapper "1" -- "1" std::vector<int> : contains
    note for MyVectorWrapper "Custom non-member swap function is defined in the same namespace to enable ADL." 

Class diagram illustrating MyVectorWrapper and its internal std::vector.

In summary, std::swap is your go-to for general-purpose swapping, leveraging move semantics or custom overloads. std::exchange is perfect for atomically replacing a value and retrieving the old one. For user-defined types with complex resource management, a custom swap operator can provide significant performance benefits and improve exception safety. Choosing the right tool for the job is key to writing effective C++.