Question
When to Use noexcept in C++: Practical Rules, Performance, and Best Practices
Question
In C++, the noexcept keyword can be added to many function declarations, but it is not always clear when it should be used in practice.
I understand that noexcept became especially important for cases such as move constructors, where throwing can affect how standard library containers behave. However, I still have several practical questions:
- If I have functions that I know will never throw, but the compiler cannot prove that fact, should I mark all of them with
noexcept? - It seems impractical to analyze every single function declaration in a codebase. In which situations should I be especially careful to use
noexcept, and when is leaving the default behavior acceptable? - When does
noexceptrealistically improve performance? - Can you give an example where adding
noexceptlets a C++ compiler generate better code? - Do modern C++ compilers and the standard library actually take advantage of
noexceptfor optimization, especially around move operations?
For example, consider a simple function like this:
int square(int x) noexcept {
return x * x;
}
and compare it with a similar function without noexcept:
int square(int x) {
return x * x;
}
When does the noexcept promise matter in real code, and how should I decide where to use it?
Short Answer
By the end of this page, you will understand what noexcept means in C++, when it is worth adding, why it matters for move constructors and standard containers, and where it usually does not produce meaningful speedups. You will also learn practical rules for using it safely in real codebases.
Concept
noexcept is a promise: this function will not allow an exception to escape.
A function marked noexcept can still contain code that might throw internally, but if an exception does escape the function, the program calls std::terminate() instead of normal exception propagation.
void f() noexcept {
throw 42; // Compiles, but if this exception escapes, the program terminates.
}
That means noexcept is not just documentation. It changes program behavior.
Why noexcept matters
In modern C++, noexcept is most important in places where the standard library or generic code needs to decide between:
- moving an object
- copying an object
- or refusing an operation entirely
A classic example is std::vector. When a vector grows and must relocate its elements, it prefers moving them for efficiency. But if moving might throw and copying is available, the vector may choose copying instead, because copying can preserve stronger exception safety guarantees.
If a type has a move constructor marked noexcept, containers can safely use it during reallocation.
Mental Model
Think of noexcept as putting a sealed "no exceptions may leave this room" sign on a function.
- Without the sign, an exception can leave the room and be handled somewhere else.
- With the sign, if an exception tries to leave, the building alarm goes off and the program is immediately stopped with
std::terminate().
Why would you ever want that sign?
Because other parts of the program can trust it.
For example, a std::vector moving elements during resize wants to know:
- "If I move these objects, can something fail halfway through?"
- If the answer is no, it can move confidently.
- If the answer is maybe, it may choose a safer but slower strategy, such as copying.
So noexcept is not mainly about saving a few CPU instructions. It is about giving strong guarantees that other code can rely on.
Syntax and Examples
Basic syntax
void logMessage() noexcept {
// guaranteed not to let exceptions escape
}
You can also make it conditional:
template <typename T>
void swapValues(T& a, T& b) noexcept(noexcept(T(std::move(a)))) {
T temp = std::move(a);
a = std::move(b);
b = std::move(temp);
}
The inner noexcept(...) checks whether an expression can throw. The outer noexcept(...) uses that result to declare whether the function itself is non-throwing.
Beginner-friendly example
#include <iostream>
class Counter {
public:
Counter(int v) noexcept : value(v) {}
{
value;
}
{
++value;
}
:
value;
};
{
;
c.();
std::cout << c.() << ;
}
Step by Step Execution
Consider this example:
#include <iostream>
#include <vector>
class Item {
public:
Item() = default;
Item(const Item&) {
std::cout << "copy\n";
}
Item(Item&&) noexcept {
std::cout << "move\n";
}
};
int main() {
std::vector<Item> items;
items.reserve(1);
items.push_back(Item{});
items.push_back(Item{});
}
What happens step by step
1. items.reserve(1);
The vector allocates enough space for 1 Item.
2. items.push_back(Item{});
A temporary Item is created.
The vector places it into its storage. Since the temporary is an rvalue, moving is used.
Output:
Real World Use Cases
1. Move-enabled classes stored in containers
If you write a class that owns resources and instances will live inside std::vector, std::deque, or similar containers, noexcept on move operations is often important.
Examples:
- file buffer wrappers
- image or audio buffers
- network packet objects
- parsed data records
2. swap functions
A custom swap is often expected to be non-throwing.
class Widget {
public:
void swap(Widget& other) noexcept {
std::swap(id, other.id);
}
private:
int id{};
};
Algorithms and generic code often work better when swap is known not to throw.
3. Destructors and cleanup logic
Destructors should not throw. In modern C++, destructors are effectively non-throwing by default unless declared otherwise, and throwing from a destructor during stack unwinding is dangerous.
Typical examples:
- releasing memory
- closing file handles
- unlocking mutexes
Real Codebase Usage
In real projects, developers usually do not try to mark every function noexcept.
Instead, they focus on places where it matters most.
Common patterns
1. Mark move operations noexcept when true
class Session {
public:
Session(Session&&) noexcept = default;
Session& operator=(Session&&) noexcept = default;
};
This is one of the most common and valuable uses.
2. Mark swap as noexcept
class Session {
public:
void swap(Session& other) noexcept {
std::swap(handle, other.handle);
}
private:
int handle{};
};
3. Use conditional noexcept in templates
Common Mistakes
1. Marking a function noexcept just because it usually does not throw
A function should be noexcept only if you are willing to make a strong promise that exceptions will never escape.
Broken example:
std::string readFile(const std::string& path) noexcept {
std::ifstream file(path);
if (!file) {
throw std::runtime_error("Cannot open file");
}
return "data";
}
If an exception escapes, the program terminates.
Better:
std::string readFile(const std::string& path) {
std::ifstream file(path);
if (!file) {
throw std::runtime_error("Cannot open file");
}
return "data";
}
2. Assuming noexcept automatically makes code faster
Comparisons
| Concept | Meaning | If exception escapes | Common use |
|---|---|---|---|
| No specification | Function may throw | Normal exception propagation | General-purpose functions |
noexcept | Function promises not to let exceptions escape | std::terminate() | Move ops, swap, accessors, low-level utilities |
noexcept(condition) | Promise depends on a compile-time condition | std::terminate() when declared non-throwing | Templates and generic code |
noexcept vs old-style throw()
| Feature |
|---|
Cheat Sheet
Quick rules
noexceptmeans: no exception may escape this function.- If an exception does escape, the program calls
std::terminate(). - Use it when non-throwing behavior is part of the function contract.
- Most valuable for move operations,
swap, destructors, and trivial accessors. - Do not add it to functions that may reasonably need to throw.
Syntax
void f() noexcept;
void g() noexcept(true);
void h() noexcept(false);
template <typename T>
void func() noexcept(noexcept(T{}));
Best candidates
class A {
:
(A&&) = ;
A& =(A&&) = ;
{ v; }
{
std::(v, other.v);
}
:
v{};
};
FAQ
What does noexcept do in C++?
It declares that a function will not let exceptions escape. If one does, the program terminates instead of propagating the exception.
Should I mark every non-throwing function noexcept?
No. Focus on functions where the guarantee matters: move operations, swap, destructors, and simple accessors. For many ordinary functions, leaving it off is fine.
Does noexcept improve performance?
Sometimes, but usually indirectly. The most important real effect is that standard containers may use move operations more aggressively when those moves are noexcept.
Why does std::vector care about noexcept?
During reallocation, vector wants to preserve exception safety. If moving elements might throw, it may choose copying instead. If moving is noexcept, moving becomes safer to use.
Can a noexcept function still contain code that throws?
Yes. But if an exception escapes the function, std::terminate() is called.
Is noexcept checked at compile time?
The declaration is checked syntactically, but the compiler does not prove all code paths are non-throwing in the general case. You are making a promise.
Mini Project
Description
Build a small movable C++ type and store it in a std::vector to observe how noexcept affects container behavior during reallocation. This project demonstrates one of the most practical reasons to use noexcept in everyday C++: enabling efficient and safe moves.
Goal
Create a class with copy and move operations, push objects into a vector, and observe when moves are preferred because the move constructor is marked noexcept.
Requirements
- Create a class that prints when it is copied or moved.
- Store objects of that class in a
std::vector. - Trigger vector reallocation by pushing multiple elements.
- Mark the move constructor and move assignment operator with
noexcept. - Compare the behavior with and without
noexcepton the move operations.
Keep learning
Related questions
Advantages of Brace Initialization in C++
Learn why C++ brace initialization is often clearer and safer than other object initialization styles, with examples and common pitfalls.
Basic Rules and Idioms for Operator Overloading in C++
Learn the core rules, syntax, and common idioms for operator overloading in C++, including member vs non-member operators.
C++ Aggregates, Trivial Types, Trivially Copyable Types, and PODs Explained
Learn what aggregates, trivial types, trivially copyable types, and PODs mean in C++, how they differ, and why they matter.