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.
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.