skip to content
Mehdi Mehdikhani
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 reallocation

Without 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:

  1. Destructors: Should always be noexcept (they are by default):
class Resource {
public:
~Resource() noexcept { // Destructors should never throw
cleanup_resource();
}
};
  1. 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;
}
};
  1. Swap functions: Should always be noexcept for 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);
}
};
  1. 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 templates
template<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 noexcept
void set_flag(bool value) noexcept { flag_ = value; }
int get_id() const noexcept { return id_; }
void swap_data(MyClass& other) noexcept { /* ... */ }
// Don't mark these noexcept
std::string format_message(const std::string& fmt); // String operations can throw
void process_file(const std::string& filename); // File operations can fail

noexcept is about making performance-critical operations faster and APIs more explicit. Use it thoughtfully, not everywhere.