noexcept
noexcept is both a specifier that promises a function throws nothing, and an operator that asks at compile time whether an expression can throw.
Why it matters
It is not just documentation: the standard library queries noexcept to pick faster code paths. vector only moves elements during reallocation if the move constructor is noexcept — otherwise it copies, to preserve the strong guarantee (exception-safety-guarantees). Marking move operations and swap noexcept is therefore a concrete performance lever, not a stylistic choice. It also lets the compiler omit exception-unwinding tables for that function.
How it works
| Form | Meaning |
|---|---|
void f() noexcept; | f promises not to throw |
void f() noexcept(B); | conditional: no-throw iff B is true |
noexcept(expr) | operator → true/false, does not evaluate expr |
- If a
noexceptfunction does throw, the runtime callsstd::terminateimmediately — there is no way to catch it outside. It is a hard promise. - Destructors and deallocation functions are implicitly
noexceptsince C++11; so are compiler-generated move/copy ops when their members don’t throw. - The conditional form propagates:
swap(a,b) noexcept(noexcept(a.swap(b)))is no-throw exactly when the member swap is. noexceptis part of the type since C++17 — avoid()noexceptpointer is distinct fromvoid(), and you cannot assign a throwing function to anoexceptpointer.
Example
Conditional noexcept makes a wrapper’s move “transparent” — no-throw only when the underlying member is:
template <class T>
struct Box {
T value;
Box(Box&& o) noexcept(std::is_nothrow_move_constructible_v<T>)
: value(std::move(o.value)) {}
};
static_assert(noexcept(Box<int>{Box<int>{}})); // int move can't throw
// Box<std::list<int>> move is also noexcept; a throwing-move T would be falseBecause the spec is conditional, std::vector<Box<int>> reallocation uses moves, not copies.
Pitfalls
- A lie is fatal, not a bug report. Declaring
noexceptthen throwing skips all handlers and callsterminate— you cannot recover. Only promise it when truly true. - Operator vs specifier confusion:
noexcept(f())is the query operator (yields a bool);void g() noexcept(f())uses that bool as the condition. Easy to misread. - Unconditionally marking move ops
noexcepton a type with a throwing member is the lie above; use theis_nothrow_*traits to stay honest. - Forgetting
noexcepton a cheap move quietly forcesvectorto copy — invisible until you profile reallocation-heavy code.