noexcept is a promise. When you put it on a function, you're telling the compiler and every reader of your code that the function will not let an exception escape. The compiler trusts that promise, generates simpler code around the call site, and the standard library uses the promise to pick faster algorithms for things like vector growth and container moves. This lesson covers the syntax, the operator, why move constructors depend on it, what happens when a noexcept function throws anyway, and how to write functions whose noexcept status depends on the operations they perform inside.
A function marked noexcept is one that the programmer guarantees will not propagate an exception out of itself. The function may still call code that throws internally, but only if every such throw is caught and handled inside the function. If an exception ever does try to leave a noexcept function, the program calls std::terminate immediately. There is no second chance and no handler higher up the stack can catch it.
Output:
The noexcept keyword goes after the parameter list, in the same slot as const on a member function or the trailing return type. The function above does nothing that could throw, so the marker is honest. The compiler now knows that any code calling confirmOrder does not need exception-handling scaffolding around the call.
noexcept is not just documentation. It changes what the standard library does with your types. When std::vector runs out of capacity and has to move its contents to a larger block, it checks whether the element type's move constructor is noexcept. If yes, it moves. If no, it falls back to copying, which is correct but often much slower. That single check is the most common reason C++ code cares about noexcept.
noexcept appears in two places: on a function declaration and as an operator that asks a question about an expression. The declaration form is the one most code uses.
Output:
A function with no noexcept annotation is treated as potentially-throwing. The exception is destructors, which are implicitly noexcept by default in C++11 and later. A destructor needs an explicit noexcept(false) to be marked as throwing, and writing destructors that throw is a bad idea regardless. The language gives destructors noexcept by default, so any throw from a destructor calls std::terminate.
noexcept can also take a constant boolean expression in parentheses: noexcept(true) means the same as plain noexcept, and noexcept(false) means "potentially throws", which is the default. Writing noexcept(true) explicitly is rare in normal code. The form becomes useful only in templates, covered in the conditional section later in this lesson.
Quick Check: Which of these functions does the compiler treat as potentially-throwing?
void send() noexcept;void send() noexcept(true);void send();<details> <summary>Answer</summary>
C. A function with no noexcept annotation is potentially-throwing by default. Options A and B both promise the function will not throw (they mean the same thing).
</details>
There's a second use of the same keyword: as a compile-time operator that asks "is this expression non-throwing?" It looks identical to the function annotation but takes an expression argument and returns a constexpr bool.
Output:
The last line is unexpected. noexcept(expr) does not actually call expr. It only looks at the types and declarations involved and asks whether any of them are declared as potentially-throwing. The + operator for std::string is not marked noexcept in the standard, so the operator should answer false. Some standard library implementations have additional non-standard overloads, so the result is implementation-defined and should not be relied on for arbitrary expressions. The reliable use of the operator is on calls to functions you control or to standard library functions that have a documented noexcept status.
The operator is mostly used to forward a noexcept guarantee from one function to another, which is the conditional pattern covered later in this lesson. Use it on hand-written expressions only when you're sure of the answer.
The most practical reason to learn noexcept is what it does to move constructors. When a std::vector grows past its current capacity, the implementation has to allocate a new block and transfer the existing elements over. There are two ways to transfer them: move each element from the old block to the new one, or copy each element. Moves are usually faster than copies.
Exception safety complicates this. If the move constructor of element 17 throws halfway through the transfer, the vector is now in a broken state. The old block still has its first 17 elements partially-moved-from. The new block has 16 successful moves and a half-finished slot. The standard requires that vector growth not corrupt the container, so the implementation needs to know in advance whether moving is safe.
noexcept provides the signal. If the element type's move constructor is noexcept, the vector knows the moves cannot throw and uses them. If not, the vector falls back to copying, which has different rollback semantics: a copy that throws leaves the old block untouched, and the partially-built new block can be discarded.
Output:
Two elements get transferred during the reallocation. With Slow, both transfers use the copy constructor because the move isn't marked noexcept. With Fast, both use the move because noexcept told the vector that moving is safe.
A vector of 100,000 strings without a noexcept move constructor would fall back to copying every string on each reallocation. For non-trivial element types this can mean orders of magnitude slower vector growth.
The same rule applies to other relocation-style operations in the standard library: std::vector::resize, std::vector::insert, and several utility functions check the move's noexcept status. Whenever you write a class that owns resources and has a hand-written move constructor, mark it noexcept if it doesn't throw. Most move constructors don't: they swap pointers, exchange ownership flags, or copy POD members. None of that allocates.
std::string's own move constructor is noexcept, so the member-wise move inside CustomerProfile's move constructor is also non-throwing. Marking the surrounding constructor noexcept is honest.
Quick Check: A class has a move constructor that allocates a small buffer with new. Should it be marked noexcept?
new can throw std::bad_alloc.<details> <summary>Answer</summary>
B. new is potentially-throwing. Marking the constructor noexcept would be incorrect. If the allocation ever does fail and throw, the program calls std::terminate. Either don't mark it, or rewrite the move so it doesn't allocate (the usual approach: take ownership of a pointer and leave the source nulled out).
</details>
The contract is enforced at runtime by std::terminate. If an exception ever tries to leave a function marked noexcept, the program does not unwind the stack and does not look for a handler. It calls std::terminate immediately, which by default calls std::abort, which kills the process.
Output (typical):
The exact message depends on the standard library and the platform, but the behaviour is the same: the catch in main never runs and the process exits. This is by design. The whole point of noexcept is that callers can skip the exception-handling machinery. If a function were allowed to break its promise without consequence, the optimisation would be unsound.
There is one exception to this rule. If you throw an exception inside a noexcept function and catch it inside that same function, the program is fine. The promise is only about exceptions leaving the function, not about exceptions occurring at all.
Output:
std::stod throws on bad input. The try/catch inside parsePrice absorbs the exception, so nothing escapes the function. The noexcept promise is intact.
The diagram shows the two outcomes for a noexcept function. A non-throwing path returns normally. A throw that gets caught inside the function is fine; the cleanup is local. A throw that escapes calls std::terminate, period. No stack unwinding past the function boundary, no catch handler higher up.
C++11 changed the default for destructors. Every destructor is implicitly noexcept unless one of its members or base classes has a destructor marked noexcept(false), or you write noexcept(false) yourself. This is the language enforcing a long-standing rule: destructors should not throw.
The destructor of LooksFine is implicitly noexcept. The throw inside it does not propagate; it kills the program. The reason for this default goes back to stack unwinding. If a destructor throws while the stack is already being unwound for another exception, the program has two exceptions in flight at once and the language has no good answer for what to do. The chosen rule is to forbid the situation by making destructors non-throwing by default.
To write a throwing destructor (almost never a good idea), opt out explicitly:
The opt-out exists for completeness. Real code should not use it. A destructor that needs to report an error has a design problem: the operation that produces the error should happen in a separate close() or flush() method called explicitly by the user, not buried in a destructor that runs during cleanup.
In template code, whether a function throws depends on what type it's instantiated with. A function that copies its argument throws if the copy throws; one that moves its argument throws if the move throws. Marking the wrapper as noexcept unconditionally would be a lie for some types and overly pessimistic for others. The fix is the conditional form: noexcept(some_boolean_expression).
The expression is usually noexcept(expr), which asks whether expr is non-throwing. That gives the nested form noexcept(noexcept(...)) that looks awkward but reads cleanly once parsed: the outer noexcept says "this function is non-throwing if the inner expression is", and the inner noexcept asks "is this expression non-throwing?"
Output:
The outer noexcept(...) is the function annotation. Its argument is noexcept(target = std::move(source)), which is the operator form asking "is the assignment inside non-throwing?" For int, assignment is non-throwing. For std::string, move assignment is noexcept. Both instantiations of the template come out as noexcept because the operation they wrap is.
The same pattern shows up all over the standard library. std::swap is noexcept exactly when the underlying type's move constructor and move assignment operator both are. std::vector's move constructor is noexcept. Container swap is noexcept if the allocator's swap is. These guarantees compose, and the conditional form is what makes that possible.
This is the same idea written with type traits from <type_traits> instead of the noexcept operator. Both forms work; type traits are clearer when the condition involves more than one operation, and the bare noexcept(noexcept(...)) form is shorter for a single-operation wrapper. Pick whichever reads better.
Quick Check: What does the outer noexcept in the following declaration evaluate to?
true, because std::string assignment is fast.false, because copy assignment can allocate and throw.<details> <summary>Answer</summary>
B. Copy assignment on std::string can allocate to hold the new contents, which means it can throw std::bad_alloc. The inner noexcept(dest = src) evaluates to false, so the outer one also marks the function as potentially-throwing.
</details>
The rule of thumb is honesty first, then enable performance second. Mark a function noexcept when it really won't throw, and leave it unmarked when it might. The compiler can't help you here; the runtime check is std::terminate, which is too late.
Functions that should almost always be noexcept:
swap exchanges pointers and small built-ins. It must not throw, or many algorithms that rely on swap lose their exception guarantees.noexcept already; don't opt out without a very strong reason.int id() const noexcept, const std::string& name() const noexcept. These return a value or a reference and don't allocate, so marking them is honest.fread and translates the return code into a bool doesn't throw.Functions where noexcept is the wrong choice:
operator new, std::vector::push_back when it has to grow, std::string concatenation. All can throw std::bad_alloc.noexcept.std::function, a virtual method) whose noexcept status you don't control. Even if your code doesn't throw, the callback might.Output:
CartLine shows the typical split for a value type: the constructor that validates input is potentially-throwing, the move operations are noexcept, the accessors are noexcept, and the destructor is implicitly noexcept. A std::vector<CartLine> can now grow efficiently by moving rather than copying.
A useful check before adding noexcept to a function: consider whether the function could throw. Is there a path through its body, including calls to other functions and operators on its members, that could allocate or call into user-provided code? If yes, don't mark it. If no, mark it.
Q1: What does `noexcept` do, and what happens if a `noexcept` function throws anyway?
noexcept is a promise from the programmer to the compiler that the function will not let an exception propagate out of it. The compiler can use that promise to generate simpler code at call sites and the standard library uses it (most visibly in std::vector reallocation) to pick faster algorithms. If an exception ever tries to leave a noexcept function, the program calls std::terminate immediately, with no stack unwinding past the function boundary and no chance for a handler higher up to catch it.
Q2: Why is marking a move constructor `noexcept` important for `std::vector`?
When a vector reallocates (grows past capacity, for example), it has to transfer its elements to a new block. If the element type's move constructor is noexcept, the vector moves the elements, which is usually much cheaper than copying. If the move is potentially-throwing, the vector falls back to copying because copy has cleaner rollback semantics if an exception occurs mid-transfer. A missing noexcept on a move constructor can switch a vector from O(n) cheap moves to O(n) expensive copies on every growth, with no warning.
Q3: What's the difference between `noexcept` as a specifier and `noexcept` as an operator?
The specifier (e.g. void f() noexcept) is a promise: it's part of the function's declaration and says the function won't throw. The operator (e.g. noexcept(expr)) is a compile-time query that returns a constexpr bool: it asks "is this expression non-throwing?" without actually evaluating the expression. The operator only looks at the declared noexcept status of the functions and operators involved. The most common combined use is noexcept(noexcept(expr)), where the outer is the specifier and the inner is the operator, used to forward the noexcept guarantee through a template.
Q4: Are destructors `noexcept` by default? Why?
Yes, since C++11 every destructor is implicitly noexcept unless it (or one of its members or base classes) is explicitly marked noexcept(false). The reason is stack unwinding. If a destructor throws while the stack is already being unwound for another exception, there are two exceptions in flight at once and the language has no good way to recover. The chosen rule is to forbid the situation: a throw from a destructor calls std::terminate. The implicit noexcept makes this enforcement automatic instead of relying on programmer discipline.
Q5: When would you write `noexcept(false)` on a function?
Almost never in normal code. The default for a function is already potentially-throwing, so writing noexcept(false) is redundant for regular functions. The one place it does something is on a destructor, where the implicit default is noexcept(true) and writing noexcept(false) opts back into being potentially-throwing. Even there, a throwing destructor indicates a design problem. The other useful place is inside a conditional specifier like noexcept(condition), where the condition evaluates to false, meaning "this function might throw when instantiated with these types". That use is generated by templates rather than written by hand.
Exercise 1: Write a function applyTax that takes a price and a tax rate as double arguments and returns the price with tax added. Mark it noexcept if it really is non-throwing. Use it in main to compute the taxed price of a $29.99 item at a 7.5% tax rate.
Expected Output:
<details> <summary>Solution</summary>
applyTax only does floating-point arithmetic, which doesn't throw. Marking it noexcept is honest.
</details>
Exercise 2: What does this code print?
Expected Output:
<details> <summary>Solution</summary>
noexcept(f()) is the operator form. It asks whether f() is declared noexcept. It is, so the answer is true. g() has no annotation, which means it's potentially-throwing, so noexcept(g()) is false. The functions are never actually called; the operator works at compile time.
</details>
Exercise 3: Fix the bug. The move constructor below should be marked noexcept so that std::vector<Wishlist> uses moves rather than copies on reallocation. Add the annotation and any other consequences.
Expected Output:
<details> <summary>Solution</summary>
std::vector<std::string>'s move constructor is noexcept, so the member-wise move in Wishlist is too. Adding noexcept makes std::vector<Wishlist> choose moves on reallocation.
</details>
Exercise 4: Will this program terminate normally or call std::terminate?
Expected Output:
(exact wording is implementation-defined; the program aborts)
<details> <summary>Solution</summary>
recordSale is noexcept. The throw inside it tries to leave the function, which calls std::terminate. The catch (...) in main never runs because no stack unwinding takes place past the noexcept boundary.
</details>
Exercise 5: Write a templated swapValues function that swaps two references. Use conditional noexcept so the function is noexcept exactly when the underlying type's move constructor and move assignment are both non-throwing. Test it with int and std::string.
Expected Output:
<details> <summary>Solution</summary>
The conditional uses type traits from <type_traits> to ask the right questions. For int and std::string, both moves are noexcept, so the wrapper inherits that guarantee.
</details>
Exercise 6: Predict the output of this program.
Expected Output:
<details> <summary>Solution</summary>
o.id() calls a member function declared noexcept, so the operator answers true. std::string("hi") + "!" involves a string constructor and operator+, both of which can allocate and are not declared noexcept, so the operator answers false.
</details>
Exercise 7: Modify the destructor below so that the program prints cleanup ok instead of terminating.
Expected Output:
<details> <summary>Solution</summary>
A destructor must not throw. The original version called std::terminate because the destructor is implicitly noexcept. The fix is to handle the failure locally (log it, set a flag for the next caller to inspect) rather than propagating it. If the program really needs to report a close failure, add an explicit close() method that the user calls before the object is destroyed.
</details>
Exercise 8: Write a class Coupon with a noexcept move constructor and move assignment. The class holds a std::string code_ and a double percentOff_. Confirm with the noexcept operator that both moves are non-throwing.
Expected Output:
<details> <summary>Solution</summary>
Both moves only involve std::string move (which is noexcept) and double copy (which is trivially non-throwing). Marking the operations noexcept is honest, and the operator confirms it.
</details>
10 quizzes