The noexcept specifier, added in C++11, is a promise to the compiler and readers of the code: this function will not throw any exceptions. That promise enables performance optimizations in the standard library (especially in std::vector) and makes failure modes easier to reason about. When the promise is broken at runtime, the program does not recover. It calls std::terminate and dies.
Attach noexcept to the end of a function declaration to mark it as non-throwing.
The function returns 0 for an unknown product instead of throwing. This is the point of the specifier: the function uses return values for error signaling, not exceptions, and the declaration tells the compiler so.
noexcept is part of the function's contract, not its implementation. The compiler does not check whether the function body actually throws. It trusts the declaration. A function marked noexcept that throws anyway pays the cost at runtime.
Two equivalent ways to write the specifier:
And one way to explicitly mark a function as potentially throwing:
A function with no specifier at all is treated the same as noexcept(false). The default in C++ is "may throw"; the non-throwing promise is opt-in.
If a function marked noexcept does throw, the runtime calls std::terminate. By default, std::terminate calls std::abort, which kills the process. No catch block can intercept the exception, not even one wrapped around the call site. Stack unwinding may or may not happen, which means destructors of local objects may be skipped.
The catch block never fires. The program aborts before control can return to the caller. This is intentional: marking a function noexcept is a binding contract, and breaking it is treated as a programming error so severe that the language refuses to let execution continue.
Why is the behavior this strict? Because code that relies on noexcept (notably containers like std::vector) makes decisions based on the promise. If a "noexcept" move constructor could throw without warning, those decisions could leave containers in a corrupted state. Failing loud and fast is safer than letting that happen.
Throwing from a noexcept function aborts the whole program. There's no way to recover, log, or retry. If a function might throw, do not mark it noexcept. Use noexcept only when no exception can escape the function body.
The same keyword has a second, completely different role. As an operator, noexcept(expr) evaluates at compile time and produces a bool. It is true if the compiler can prove that expr will not throw, and false otherwise.
Three details matter here:
noexcept(...) is not evaluated. The compiler only inspects the types and specifications involved. chargeCard() is never actually called here, which is why the program doesn't terminate even though that function would throw if invoked.noexcept(2 + 3) is true.constexpr bool, usable anywhere a compile-time bool is allowed, including static_assert, if constexpr, and template parameters.The same word, noexcept, plays two different roles depending on where it appears:
| Role | Syntax | Meaning |
|---|---|---|
| Specifier | void f() noexcept; | Function declares it won't throw |
| Operator | noexcept(f()) | Compile-time check: would f() throw? |
They can be nested, which is the basis for the next idea.
The specifier can take a compile-time boolean expression, not just a fixed true or false. The value can be computed at compile time, often from the noexcept-ness of some other operation. This is conditional noexcept.
The canonical example is generic code that wraps another operation:
The outer noexcept(...) is the specifier. The inner noexcept(std::swap(a, b)) is the operator. The line reads: "this function is non-throwing if and only if calling std::swap(a, b) is non-throwing". For int, swap is non-throwing, so swapTwo<int> is also non-throwing. For a type whose swap can throw, swapTwo propagates that fact and is itself potentially throwing.
This pattern appears throughout the standard library. Move constructors, swap functions, and many other generic operations carry their noexcept-ness from the operations they depend on. Writing noexcept(true) unconditionally would misrepresent types whose operations can throw. Writing noexcept(false) always would give up the optimization opportunities for types where the promise can be kept. Conditional noexcept handles both cases correctly.
This is the main practical reason noexcept matters. When std::vector runs out of capacity and has to reallocate, it can either move the existing elements into the new storage or copy them. Moving is almost always faster, especially for elements that own heap memory.
std::vector will only use moves during reallocation if the move constructor of the element type is marked noexcept. If the move constructor could throw, the vector falls back to copying. The reason has to do with the strong exception guarantee: if reallocation fails partway through, the vector wants the original storage to remain unchanged. With copies, that's easy. With moves, if one element's move throws halfway through, the original elements have already been wrecked and there's no way back. To avoid that scenario, the vector refuses to use moves it cannot trust.
A Product class with a noexcept move constructor, instrumented to count copies and moves:
The vector moved the two existing Product objects into the new buffer instead of copying them. What happens if the move constructor is not noexcept? Change the declaration to drop the noexcept:
Recompile and rerun. The output flips:
Same program, same data, very different performance characteristics. For a small Product with a single std::string member, the difference is small. For a vector of objects that each hold a large allocation (or worse, a network connection that gets recreated on copy), the cost of forgetting noexcept on the move constructor can be substantial.
The decision tree the vector follows looks like this:
The vector prefers the move path on the left only when the noexcept promise is in place. Otherwise it takes the safer but slower copy path. If the type isn't even copy-constructible (such as a class with a std::unique_ptr member and no copy ctor), the vector uses the throwing move and gives up the strong exception guarantee.
Forgetting noexcept on a move constructor turns vector growth from O(n) moves into O(n) copies. For elements holding heap allocations, that can be a 10x to 100x slowdown on resize. This is a common performance bug caused by missing noexcept.
The same rule applies to move assignment operators inside containers that use them for reordering or swapping, and to swap functions used by algorithms like std::sort. When writing a move constructor for a class that will end up in a std::vector, mark it noexcept if possible.
The move constructor is declared noexcept, but its body calls formatName, which throws when the name is empty. As soon as formatName throws, the runtime sees an exception escaping a noexcept function and calls std::terminate. The program aborts. No catch block can rescue this. The compiler did not catch the inconsistency at compile time because it never tries to prove whether a noexcept function actually keeps the promise. It trusts the declaration.
Fix: either remove the noexcept (and accept that the move can throw, which means containers will copy instead of move), or make the body non-throwing. The second option is usually preferred:
std::string's move constructor is noexcept, so moving the name from other into the new object cannot throw. The new constructor honors its declaration.
Destructors are implicitly noexcept since C++11. The specifier doesn't need to be written, and overriding it is almost never correct.
The reason is that throwing from a destructor during stack unwinding is catastrophic. If an exception is already in flight and another one escapes a destructor while the stack is being cleaned up, the program calls std::terminate. The standard library and language design treat destructors as a place where exceptions must not escape, so the implicit noexcept(true) makes that the default.
Writing ~Order() noexcept(false) { ... } is legal and makes the destructor potentially throwing. Don't. Code that does this is almost always broken, and standard library containers refuse to work safely with such types. If a cleanup operation might fail, log the failure, swallow the exception inside the destructor, and move on:
The destructor follows the rule by catching anything close() might throw and absorbing it. The destructor itself remains non-throwing.
Before C++11, the language had a different mechanism called dynamic exception specifications. Older codebases contain code like this:
These specifications had three big problems. They were checked at runtime, not compile time, which meant they often added overhead instead of removing it. The behavior on violation was awkward (it called std::unexpected, a now-removed handler). And they did not compose well with generic code, where the types a template might throw are often unknown in advance.
| Specification | Status | Equivalent today |
|---|---|---|
throw() | Deprecated in C++11, removed in C++17 | noexcept |
throw(T) | Deprecated in C++11, removed in C++17 | No replacement, just remove it |
noexcept | Current, C++11 onward | (this is the replacement) |
noexcept(true) | Current | Same as noexcept |
noexcept(false) | Current | Same as no specifier |
throw() in legacy code is old-style noexcept. Don't write it in new code. C++17 removed the syntax entirely (with the narrow exception of throw() itself, which was kept temporarily as a deprecated alias for noexcept and finally removed in C++20).
Before C++17, the noexcept specification was treated as a property of the function declaration but not as part of the function's type. Two function pointers pointing to functions with different noexcept-ness were the same type.
C++17 changed that. A function pointer to a noexcept function is now a different type from a pointer to a throwing function. This matters mainly when assigning or passing function pointers around.
The conversion from noexcept function pointer to throwing function pointer is allowed because it loosens the promise (anything non-throwing also satisfies the weaker "may throw" contract). The reverse is not allowed, because the compiler cannot strengthen a function's contract on its own. If it could, code expecting a non-throwing callback might end up calling a function that actually throws, breaking every guarantee built on top of it.
In practice, this rule matters most when building callback systems with function pointers, when storing callable objects in std::function (which has its own quirks around noexcept), or when taking the address of an overloaded function.
A small Cart class that uses noexcept thoughtfully:
Design decisions in this class:
addItem is not noexcept, because vector::push_back can throw std::bad_alloc if memory runs out.clear is noexcept because all it does is destroy elements (whose destructors are themselves noexcept) and reset internal pointers.size is noexcept because it's a trivial read of an integer member.noexcept so a std::vector<Cart> will move rather than copy on reallocation.The pattern is: be honest. Mark a function noexcept when the promise can be kept, leave it off otherwise. Don't add it blanket-style for cosmetic reasons, and don't omit it on operations that never throw (especially move operations).
10 quizzes