noexcept — Exception Specifications
"noexcept specifier and operator: marking functions non-throwing, conditional noexcept, move semantics optimization, and type-system implications since C++17."
noexceptsince C++11noexcept is a function specifier that promises not to propagate exceptions, and a compile-time operator that queries whether an expression is non-throwing — the combination enables containers and algorithms to select move-or-copy strategies at compile time.
Overview
Before C++11, dynamic exception specifications (throw(T, U)) required runtime infrastructure to unwind the stack before calling std::terminate on violation, and the compiler could not optimize around them. C++11 replaced them with noexcept. A noexcept violation calls std::terminate immediately with no unwinding guarantee, which both simplifies the ABI and enables more aggressive optimizer decisions on code paths.
The effect on the standard library is concrete: std::vector internally uses std::move_if_noexcept during reallocation. If a type's move constructor is not noexcept, the container falls back to copying every element to preserve the strong exception guarantee. A move constructor that isn't marked noexcept silently degrades every push_back that triggers growth from O(1) moves to O(n) copies. No compiler warning, no diagnostic — just invisible regression.
Since C++17, noexcept is part of the function's type, affecting function pointer conversions, virtual function overrides, and template instantiation.
Dynamic exception specifications (including throw()) were deprecated in C++11 and removed in C++17. There is no replacement for throw(TypeList) — use noexcept for the nothrow guarantee or leave the function potentially-throwing.
Syntax
Specifier forms:
void f() noexcept; // non-throwing — C++11
void g() noexcept(true); // equivalent to noexcept
void h() noexcept(false); // potentially throwing — same as no specifierConditional noexcept — the argument must be a constant expression evaluating to bool:
template<typename T>
void move_assign(T& dst, T& src)
noexcept(std::is_nothrow_move_assignable_v<T>) { // _v alias: C++17
dst = std::move(src);
}noexcept operator — compile-time query, the expression is never evaluated:
static_assert( noexcept(1 + 2) );
static_assert(!noexcept(std::vector<int>{}.push_back(0)) );Double-noexcept idiom — propagates noexcept-ness from sub-operations without duplicating the condition:
template<typename T>
void push(T&& val)
noexcept(noexcept(data_.push_back(std::forward<T>(val)))) {
data_.push_back(std::forward<T>(val));
}The outer noexcept(...) is the specifier; the inner noexcept(...) is the operator. Together they ask: "is this particular call noexcept?" and use the answer as the function's own guarantee.
Examples
Move operations and container reallocation
class Buffer {
std::byte* data_;
std::size_t size_;
public:
// noexcept is essential — std::vector checks this before choosing move vs copy
Buffer(Buffer&& other) noexcept
: data_(std::exchange(other.data_, nullptr)) // std::exchange: C++14
, size_(std::exchange(other.size_, 0)) {}
Buffer& operator=(Buffer&& other) noexcept {
if (this != &other) {
delete[] data_;
data_ = std::exchange(other.data_, nullptr);
size_ = std::exchange(other.size_, 0);
}
return *this;
}
~Buffer() noexcept { delete[] data_; } // implicitly noexcept in C++11+
};
// Verify at compile time — catches the mistake before it silently degrades perf
static_assert(std::is_nothrow_move_constructible_v<Buffer>); // C++17 _v alias
static_assert(std::is_nothrow_move_assignable_v<Buffer>);
std::vector<Buffer> vec;
vec.reserve(4);
vec.push_back(Buffer{}); // reallocation moves, not copiesstd::move_if_noexcept
std::move_if_noexcept (C++11) returns T&& if the move constructor is noexcept, and const T& otherwise. Use it when implementing containers that must preserve the strong exception guarantee during element relocation:
template<typename T>
void safe_relocate(T* src, T* dst, std::size_t n) {
for (std::size_t i = 0; i < n; ++i)
::new(dst + i) T(std::move_if_noexcept(src[i]));
// If T's move ctor is noexcept → moves. Otherwise → copies.
// If an exception escapes mid-loop, previously constructed elements remain valid.
}This is exactly what std::vector::push_back does internally when it outgrows its capacity.
noexcept-aware swap
std::swap is conditionally noexcept since C++11. Provide an ADL-visible friend swap so generic algorithms can swap your type without copying:
class Widget {
int id_;
std::string name_;
std::vector<int> data_;
public:
friend void swap(Widget& a, Widget& b) noexcept {
using std::swap;
swap(a.id_, b.id_); // noexcept
swap(a.name_, b.name_); // std::string::swap is noexcept (C++11)
swap(a.data_, b.data_); // std::vector::swap is noexcept (C++11)
}
};
static_assert(std::is_nothrow_swappable_v<Widget>); // C++17 traitThe copy-and-swap assignment idiom depends on this: a noexcept swap gives strong exception guarantee to operator= at zero extra cost.
noexcept and the type system (C++17)
Before C++17, noexcept only appeared in the exception specification — two functions with identical signatures differing only in noexcept were considered the same type. C++17 made it part of the type:
using NE = void(*)() noexcept;
using Any = void(*)();
void ne_fn() noexcept {}
void th_fn() {}
NE p1 = ne_fn; // OK
Any p2 = ne_fn; // OK — noexcept→potentially-throwing is an implicit conversion (C++17)
// NE p3 = th_fn; // error: potentially-throwing cannot implicitly convert to noexcept
// Overloading on noexcept alone is not permitted:
// void f() noexcept; // error: redeclaration
// void f();std::function does not preserve noexcept in C++17 or C++20 — wrapping a noexcept callable in std::function<void()> silently drops the guarantee. If the no-throw property must survive erasure, use a function pointer, a template parameter, or std::move_only_function (C++23).
noexcept on lambdas
auto square = [](double x) noexcept -> double { return x * x; }; // C++11
static_assert(noexcept(square(2.0)));
// Propagated noexcept in a generic forwarding lambda (C++14 generic lambda):
auto fwd_invoke = [](auto&& fn, auto&&... args)
noexcept(noexcept(fn(std::forward<decltype(args)>(args)...)))
-> decltype(auto) {
return fn(std::forward<decltype(args)>(args)...);
};is_nothrow_* type traits
The <type_traits> header provides compile-time queries for all special operations. The traits themselves are C++11; the _v variable template aliases are C++17:
// trait::value form available in C++11; _v alias requires C++17
std::is_nothrow_default_constructible_v<T> // T() noexcept?
std::is_nothrow_copy_constructible_v<T> // T(const T&) noexcept?
std::is_nothrow_move_constructible_v<T> // T(T&&) noexcept?
std::is_nothrow_copy_assignable_v<T> // op=(const T&) noexcept?
std::is_nothrow_move_assignable_v<T> // op=(T&&) noexcept?
std::is_nothrow_destructible_v<T> // ~T() noexcept?
std::is_nothrow_swappable_v<T> // swap(T&,T&) noexcept? (C++17 trait)
std::is_nothrow_invocable_v<F, Args...> // F(Args...) noexcept? (C++17 trait)These are the building blocks for conditional noexcept in generic code. Query properties of T, derive your function's noexcept-ness from them.
Best Practices
Mark all move constructors and move assignment operators noexcept when they only transfer ownership (pointer or handle swaps, std::exchange, std::move on noexcept-movable members). This is the single highest-impact use. Verify with static_assert(std::is_nothrow_move_constructible_v<T>) in the class body — it catches the mistake at the definition site, not at a profiler six months later.
Use conditional noexcept in generic code rather than unconditional noexcept. An unconditional noexcept on a template that calls T operations can silently call std::terminate when instantiated with a throwing T:
// BAD: T::process() might throw → std::terminate with no useful context
template<typename T>
void process(T& obj) noexcept { obj.process(); }
// GOOD: propagates noexcept-ness from T
template<typename T>
void process(T& obj) noexcept(noexcept(obj.process())) { obj.process(); }Prefer is_nothrow_* traits over inline noexcept(expr) operator when the condition is about a single named operation — the trait is more readable, handles access control edge cases, and is easier to compose:
template<typename T>
T extract(std::vector<T>& v)
noexcept(std::is_nothrow_move_constructible_v<T>) {
T val = std::move(v.back());
v.pop_back();
return val;
}Catch internally when adapting potentially-throwing code under a noexcept boundary — the right pattern for wrappers over legacy or third-party code you cannot change:
void safe_flush() noexcept {
try {
legacy_stream_.flush();
} catch (...) {
// log to a side channel that cannot throw
}
}Common Pitfalls
Forgetting noexcept on move operations — no warning, silent perf regression. The only mitigation is static_assert in the class definition. Add it whenever you write a move constructor.
Unconditional noexcept on forwarding wrappers. This is the most dangerous mistake. The wrapper compiles and runs correctly until T throws, at which point std::terminate fires with a call stack that points into your wrapper rather than the actual throwing site.
noexcept does not prevent exceptions inside the function body. A noexcept function can contain try/catch blocks. The specifier only applies at the boundary: an exception that would escape the function calls std::terminate; exceptions caught locally are fine. This also means std::terminate can be called after partial local stack unwinding — not at the throw site.
Virtual overrides cannot be more permissive than the base. Since C++11, an override may not have a looser exception specification than the overridden function:
struct Base {
virtual void work() noexcept;
};
struct Derived : Base {
void work() override; // error — looser than Base::work() noexcept
};The reverse is fine — a noexcept override of a potentially-throwing base is allowed.
Destructors are implicitly noexcept in C++11+ but members are not exempt. A destructor that calls a member function that throws will call std::terminate. If a member's destructor is not noexcept (rare but possible with older C code wrapped in RAII), the containing class's destructor is no longer implicitly noexcept.