noexcept Specifier
noexcept specifies that a function won't throw exceptions, enabling optimizations and stronger guarantees.
noexcept allows compilers to optimize (no exception handling overhead) and enables move operations in standard containers. For how it underpins the strong exception guarantee, see Exception Safety.
Basic Usage
// Function that doesn't throw
void safe_function() noexcept {
// No exceptions thrown
}
// Function that might throw (default)
void risky_function() {
throw std::runtime_error("Error");
}
// Conditional noexcept (C++11)
void conditional() noexcept(sizeof(int) == 4) {
// noexcept if condition is true
}
noexcept vs throw()
// Old style (C++98, deprecated)
void old_style() throw() {
// Exception specification (avoid)
}
// Modern style (C++11+)
void modern_style() noexcept {
// Preferred
}
Difference: throw() calls std::unexpected, noexcept calls std::terminate. Both crash if exception escapes, but noexcept is more efficient.
What Happens if Exception Thrown?
void marked_noexcept() noexcept {
throw std::runtime_error("Oops"); // Compiles but dangerous!
}
int main() {
try {
marked_noexcept();
} catch (...) {
// Never reached!
}
}
// std::terminate called → program crashes
Rule: If exception escapes noexcept function, std::terminate is called immediately.
Conditional noexcept
// noexcept depends on expression
template<typename T>
void swap(T& a, T& b) noexcept(std::is_nothrow_move_constructible_v<T>) {
T temp(std::move(a));
a = std::move(b);
b = std::move(temp);
}
// Simple types
int x = 1, y = 2;
swap(x, y); // noexcept(true)
// Complex types
std::string s1 = "hello", s2 = "world";
swap(s1, s2); // noexcept(true) - string's move doesn't throw
// Custom type that might throw
struct MightThrow {
MightThrow(MightThrow&&) { /* might throw */ }
};
MightThrow a, b;
swap(a, b); // noexcept(false)
Testing for noexcept
void func1() noexcept { }
void func2() { }
// Check if function is noexcept
static_assert(noexcept(func1()), "func1 is noexcept");
static_assert(!noexcept(func2()), "func2 can throw");
// Check expressions
int x = 0;
static_assert(noexcept(x + 1), "Addition doesn't throw");
static_assert(!noexcept(std::string("hello")), "String ctor might throw");
Move Semantics and noexcept
Standard containers use move only if it's noexcept:
class Widget {
public:
// Without noexcept
Widget(Widget&& other) {
// std::vector uses COPY when growing
}
// With noexcept
Widget(Widget&& other) noexcept {
// std::vector uses MOVE when growing (efficient!)
}
};
std::vector<Widget> vec;
vec.push_back(Widget{}); // Triggers potential reallocation
Why: If move throws during vector reallocation, strong exception guarantee is broken. So vector copies instead (inefficient but safe).
Mark move constructors/assignments noexcept or standard containers won't use them!
Destructors
Destructors are implicitly noexcept:
class Widget {
public:
~Widget() { // Implicitly noexcept
// Throwing here is dangerous
}
};
// Explicitly not noexcept (rare, dangerous)
class Bad {
public:
~Bad() noexcept(false) {
throw std::runtime_error("Bad idea");
}
};
Rule: Never throw from destructors. If exception during stack unwinding, std::terminate is called.
Functions That Should Be noexcept
class Widget {
public:
// Move operations (critical!)
Widget(Widget&&) noexcept;
Widget& operator=(Widget&&) noexcept;
// Destructor (implicit)
~Widget() noexcept;
// Swap
void swap(Widget& other) noexcept;
// Simple getters
int getValue() const noexcept { return value; }
private:
int value;
};
Propagating noexcept
template<typename T>
class Wrapper {
T value;
public:
// Propagate noexcept from T's constructor
Wrapper(T v) noexcept(std::is_nothrow_move_constructible_v<T>)
: value(std::move(v)) {}
// Propagate from T's swap
void swap(Wrapper& other) noexcept(noexcept(std::swap(value, other.value))) {
std::swap(value, other.value);
}
};
Performance Impact
Without noexcept
void process() {
// Compiler must generate exception handling code
// - Setup exception tables
// - Add cleanup code
// - Maintain exception state
}
With noexcept
void process() noexcept {
// Compiler can optimize away exception handling
// - Smaller code
// - Faster execution
// - Better inlining opportunities
}
Typical improvement: 10-20% faster in hot paths, smaller binary.
Wide Contracts vs Narrow Contracts
Wide Contract (noexcept friendly)
// Accepts any input, never throws
int abs(int x) noexcept {
return x < 0 ? -x : x;
}
Narrow Contract (throws on invalid input)
// Preconditions: size > 0
int front(std::vector<int>& vec) {
if (vec.empty()) {
throw std::out_of_range("Empty vector");
}
return vec.front();
}
// Can't mark noexcept - might throw
Guideline: Wide contracts (no preconditions) can be noexcept, narrow contracts (preconditions) usually can't.
Real-World Example
class String {
char* data;
size_t len;
public:
// Move constructor - must be noexcept for container optimization
String(String&& other) noexcept
: data(other.data), len(other.len) {
other.data = nullptr;
other.len = 0;
}
// Move assignment - also noexcept
String& operator=(String&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data;
len = other.len;
other.data = nullptr;
other.len = 0;
}
return *this;
}
// Destructor - implicitly noexcept
~String() noexcept {
delete[] data;
}
// Swap - should be noexcept
void swap(String& other) noexcept {
std::swap(data, other.data);
std::swap(len, other.len);
}
};
Common Mistakes
Over-promising
void process(const std::string& s) noexcept {
std::string copy = s; // Might throw bad_alloc!
}
// If throws, std::terminate is called
Under-promising
// Could be noexcept but isn't marked
int add(int a, int b) {
return a + b; // Never throws
}
// Missed optimization opportunity
Checking Standard Library
#include <type_traits>
// Check if type's operations are noexcept
static_assert(std::is_nothrow_move_constructible_v<std::string>);
static_assert(std::is_nothrow_move_assignable_v<std::vector<int>>);
// Check specific operations
std::vector<int> vec;
static_assert(noexcept(vec.size())); // true
static_assert(!noexcept(vec.at(0))); // false (throws on out of range)
static_assert(noexcept(vec[0])); // true (no bounds checking)
Best Practices
- Mark move constructors/assignments
noexcept - Mark destructors
noexcept(implicit, but can be explicit) - Mark swap functions
noexcept - Mark simple getters
noexcept - Use conditional
noexceptfor templates
- Mark functions
noexceptif they might throw - Throw from destructors
- Throw from
noexceptfunctions (causes terminate) - Forget
noexcepton move operations
Summary
noexcept:
- Promises no exceptions
- Enables optimizations
- Required for efficient moves
- Crashes program if violated (std::terminate)
Key functions to mark:
class T {
T(T&&) noexcept; // Move constructor
T& operator=(T&&) noexcept; // Move assignment
~T() noexcept; // Destructor (implicit)
void swap(T&) noexcept; // Swap
};
Benefits:
- Better performance (10-20% in hot paths)
- Smaller binary size
- Standard containers use move operations
- Stronger exception safety guarantees
Cost: Must ensure function truly doesn't throw, or program terminates.