Engineering Insights/noexcept: Copy vs Move
Move SemanticsnoexceptSTLPerformance

noexcept: Copy vs Move

A single keyword — noexcept — can change STL container behavior from O(n) copies to O(1) pointer transfers. Here is why the C++ standard library cares deeply about exception specifications.

The Core Problem

When a std::vector runs out of capacity and reallocates, it must transfer all existing elements into the new larger buffer. The question is: should it copy or move them?

Copying is safe — if something goes wrong halfway through, the original buffer still has all its data intact. But moving is faster — it just transfers ownership of internal resources (pointers, handles) without duplicating data.

The problem: if a move throws an exception halfway through reallocation, both the old buffer (partially moved-out) and the new buffer are corrupted. You have lost data. So std::vector will only use move if the move constructor is marked noexcept.

Without noexcept — Forced to Copy

Without the noexcept specifier, the container cannot trust that the move will succeed. It conservatively copies every element — O(n) allocations each time the vector grows.

orderbook.hpp
// No noexcept — vector is FORCED to copy on reallocation
class OrderBook {
public:
    std::vector<Order> orders;

    // Move constructor WITHOUT noexcept
    OrderBook(OrderBook&& other) {
        orders = std::move(other.orders);
    }
    // std::vector cannot use this move — it must fall back to copy
    // because if an exception is thrown mid-reallocation, it needs
    // to guarantee the strong exception safety invariant.
};

With noexcept — Safe to Move

Adding noexcept is a compile-time contract: “this function will never throw.” The STL checks this at compile time using type traits and enables the O(1) move path.

orderbook_fast.hpp
// With noexcept — vector can SAFELY move on reallocation
class OrderBook {
public:
    std::vector<Order> orders;

    // Move constructor WITH noexcept — signals: "I will not throw"
    OrderBook(OrderBook&& other) noexcept {
        orders = std::move(other.orders);  // O(1) pointer transfer
    }
    // std::vector sees noexcept guarantee, picks move over copy.
    // Result: O(1) reallocation instead of O(n).
};

How the STL Decides at Compile Time

stl_vector_internals.hpp
// How std::vector decides at compile time using type traits:

if constexpr (
    std::is_nothrow_move_constructible<T>::value  // noexcept move?
    || !std::is_copy_constructible<T>::value       // or no copy?
) {
    // MOVE — O(1): transfer pointer + size + capacity
    new_buffer[i] = std::move(old_buffer[i]);
} else {
    // COPY — O(n): duplicate every element into new buffer
    new_buffer[i] = old_buffer[i];
}

“The type trait std::is_nothrow_move_constructible<T> evaluates to true only if T's move constructor is declared noexcept. This check happens entirely at compile time — zero runtime overhead.”

C++ Semantics

noexcept: Copy vs Move

C++ containers move objects only when it is guaranteed safe—determined at compile time by the noexcept specifier on the move constructor.

Copy realloc:3.0s
Move realloc:0.6s
Speedup:~5×
WITHOUT noexcept// forced to copy

old_buf [cap=4]

OB₀price
OB₁qty
OB₂side
OB₃ts
realloc →

new_buf [cap=8]

·
·
·
·

orderbook.hpp

struct OrderBook {

std::vector<Order> orders;

// no move ctor declared

// vector uses copy ctor

// on every reallocation

};

ctor

copy ctor

allocs

cost

O(n)

WITH noexcept// safe to move

old_buf [cap=4]

OB₀price
OB₁qty
OB₂side
OB₃ts
realloc →

new_buf [cap=8]

·
·
·
·

orderbook.hpp

struct OrderBook {

std::vector<Order> orders;

OrderBook(OrderBook&& o)

noexcept

{

orders = std::move(o.orders);

}

};

ctor

move ctor

allocs

cost

O(1)

Property
Without noexcept
With noexcept
Mechanism
T::copy_constructor()
T::move_constructor() noexcept
Heap allocs
5× new/delete
1× (new buffer only)
Complexity
O(n) — copies every element
O(1) — transfers ownership
Exception safety
strong (can rollback)
strong (noexcept guarantee)

One keyword can change how the entire container behaves.

When to Mark noexcept

Scenario
Recommendation
Move constructor / assignment
Always noexcept if possible
Swap operations
Always noexcept
Destructors
Implicitly noexcept — do not add throwing logic
Copy constructor
Conditional — allocations can fail
Functions with malloc/new
Not noexcept unless caught internally
Discussion

No comments yet. Be the first to contribute.