noexcept
/ 4 min read
Table of Contents
noexcept in C++11 replaced the old exception specifications with something actually useful. It tells the compiler a function won’t throw exceptions, enabling optimizations and making contracts clearer.
What noexcept Actually Does
noexcept is a promise that a function won’t throw exceptions:
void safe_function() noexcept { // This function guarantees it won't throw}
void might_throw() { // This function might throw exceptions}If a noexcept function does throw, std::terminate is called immediately - no stack unwinding.
Why This Matters
The compiler can optimize noexcept functions more aggressively:
std::vector<MyClass> vec;vec.push_back(MyClass{}); // If MyClass move constructor is noexcept, // vector can use move instead of copy during reallocationWithout noexcept, containers have to assume move operations might throw and fall back to copying for exception safety.
When I Actually Use noexcept
Most of the time, I mark functions noexcept when:
- Destructors: Should always be
noexcept(they are by default):
class Resource {public: ~Resource() noexcept { // Destructors should never throw cleanup_resource(); }};- Move operations: Critical for performance with containers:
class MyString { char* data_; size_t size_;
public: MyString(MyString&& other) noexcept : data_(other.data_), size_(other.size_) { other.data_ = nullptr; other.size_ = 0; }
MyString& operator=(MyString&& other) noexcept { if (this != &other) { delete[] data_; data_ = other.data_; size_ = other.size_; other.data_ = nullptr; other.size_ = 0; } return *this; }};- Swap functions: Should always be
noexceptfor performance:
class DataContainer {public: void swap(DataContainer& other) noexcept { std::swap(data_, other.data_); std::swap(size_, other.size_); }
friend void swap(DataContainer& a, DataContainer& b) noexcept { a.swap(b); }};- Simple utility functions: Functions that obviously can’t throw:
class Point { double x_, y_;public: double x() const noexcept { return x_; } double y() const noexcept { return y_; }
void set_x(double x) noexcept { x_ = x; } void set_y(double y) noexcept { y_ = y; }};Conditional noexcept
You can make noexcept conditional on other operations:
template<typename T>class Optional { T value_; bool has_value_;
public: Optional(T&& value) noexcept(std::is_nothrow_move_constructible_v<T>) : value_(std::move(value)), has_value_(true) {}
T& get() noexcept(std::is_nothrow_move_constructible_v<T>) { return value_; }};The function is noexcept only if the template parameter supports nothrow operations.
noexcept as a Type Trait
You can query whether a function is noexcept:
void throwing_func();void safe_func() noexcept;
static_assert(!noexcept(throwing_func()), "throwing_func can throw");static_assert(noexcept(safe_func()), "safe_func is noexcept");
// Useful in templatestemplate<typename T>void process(T&& value) noexcept(noexcept(std::forward<T>(value).process())) { std::forward<T>(value).process();}Real World Example
Here’s how I use noexcept in a cache implementation:
template<typename Key, typename Value>class LRUCache { struct Node { Key key; Value value; Node* prev = nullptr; Node* next = nullptr;
Node(Key k, Value v) : key(std::move(k)), value(std::move(v)) {} };
std::unordered_map<Key, std::unique_ptr<Node>> cache_; Node* head_ = nullptr; Node* tail_ = nullptr; size_t capacity_;
void move_to_front(Node* node) noexcept { // Moving pointers around - can't throw if (node == head_) return;
// Remove from current position if (node->prev) node->prev->next = node->next; if (node->next) node->next->prev = node->prev; if (node == tail_) tail_ = node->prev;
// Add to front node->prev = nullptr; node->next = head_; if (head_) head_->prev = node; head_ = node; if (!tail_) tail_ = node; }
public: explicit LRUCache(size_t capacity) noexcept : capacity_(capacity) {}
void clear() noexcept { cache_.clear(); head_ = tail_ = nullptr; }
size_t size() const noexcept { return cache_.size(); } bool empty() const noexcept { return cache_.empty(); }};The noexcept(false) Case
Sometimes you need to explicitly say a function can throw:
class LoggedResource {public: // Move constructor might throw if logging fails LoggedResource(LoggedResource&& other) noexcept(false) { log("Moving resource"); // This might throw take_ownership_from(other); }};Though in practice, you’d probably design this differently to avoid throwing in move operations.
The Rule I Follow
I mark functions noexcept when:
- They’re destructors, move operations, or swap functions
- They only do simple operations that can’t throw
- I’ve verified all called functions are also
noexcept
I don’t mark functions noexcept when:
- They call functions that might throw
- They do memory allocation (unless using nothrow versions)
- I’m not sure - it’s better to be conservative
// Safe candidates for noexceptvoid set_flag(bool value) noexcept { flag_ = value; }int get_id() const noexcept { return id_; }void swap_data(MyClass& other) noexcept { /* ... */ }
// Don't mark these noexceptstd::string format_message(const std::string& fmt); // String operations can throwvoid process_file(const std::string& filename); // File operations can failnoexcept is about making performance-critical operations faster and APIs more explicit. Use it thoughtfully, not everywhere.