C++ noexcept Complete Guide
이 글의 핵심
Master C++ noexcept: exception specifications, conditional noexcept, move optimization for containers, terminate on violation, and production patterns.
What is noexcept?
noexcept is a C++11 keyword specifying that a function does not throw exceptions. If it throws anyway, std::terminate is called, terminating the program.
void func() noexcept {
// Throwing causes std::terminate
}
void func2() noexcept(true) { // Same as noexcept
// ...
}
void func3() noexcept(false) { // May throw
// ...
}
Why needed?:
- Optimization: Compiler can omit stack unwinding code
- STL optimization:
std::vectorchecks noexcept move operators - Clarity: Explicitly states function won’t throw
- Safety: Required for exception-safe guarantees
// ❌ Without noexcept
class Widget {
public:
Widget(Widget&& other) {
// std::vector uses copy (safe)
}
};
// ✅ With noexcept
class Widget {
public:
Widget(Widget&& other) noexcept {
// std::vector uses move (fast)
}
};
noexcept Behavior
void func() noexcept {
throw std::runtime_error("error"); // Calls std::terminate
}
// Conceptual behavior
void func() {
try {
// Function body
throw std::runtime_error("error");
} catch (...) {
std::terminate(); // All exceptions → terminate
}
}
noexcept vs throw()
| Feature | noexcept (C++11) | throw() (C++98, deprecated) |
|---|---|---|
| On exception | std::terminate | std::unexpected → std::terminate |
| Optimization | ✅ Possible | ❌ Limited |
| Conditional | ✅ Possible | ❌ Not possible |
| Recommended | ✅ Use | ❌ Don’t use |
// ❌ throw(): deprecated
void func() throw() {
// ...
}
// ✅ noexcept: recommended
void func() noexcept {
// ...
}
Conditional noexcept
template<typename T>
void func(T value) noexcept(noexcept(T(value))) {
T copy(value);
}
Explanation: noexcept(noexcept(T(value))) checks if T’s copy constructor is noexcept. Outer noexcept is the specifier, inner noexcept is the operator returning bool.
Practical Examples
Example 1: Move Operations
class Buffer {
int* data;
size_t size;
public:
// ✅ noexcept move constructor
Buffer(Buffer&& other) noexcept
: data(other.data), size(other.size) {
other.data = nullptr;
other.size = 0;
}
// ✅ noexcept move assignment
Buffer& operator=(Buffer&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data;
size = other.size;
other.data = nullptr;
other.size = 0;
}
return *this;
}
};
Why noexcept matters: std::vector reallocation uses move if move constructor is noexcept, otherwise uses copy for exception safety. Without noexcept, performance degrades significantly.
Example 2: swap
class Widget {
public:
void swap(Widget& other) noexcept {
std::swap(data, other.data);
}
private:
int data;
};
// std::swap specialization
namespace std {
template<>
void swap(Widget& a, Widget& b) noexcept {
a.swap(b);
}
}
Example 3: Destructor
// Destructors are implicitly noexcept
class MyClass {
public:
~MyClass() noexcept { // Explicit (optional)
// Throwing causes std::terminate
}
};
Key: Destructors are implicitly noexcept unless explicitly specified noexcept(false) (not recommended).
Example 4: Conditional noexcept
template<typename T>
class Container {
public:
void push_back(T&& value)
noexcept(noexcept(data.push_back(std::move(value)))) {
data.push_back(std::move(value));
}
private:
std::vector<T> data;
};
Explanation: push_back is noexcept only if vector::push_back is noexcept. This propagates nothrow guarantee from member operations.
noexcept Operator
// noexcept(expression): returns bool at compile time
void func() noexcept {}
void func2() {}
static_assert(noexcept(func())); // true
static_assert(!noexcept(func2())); // false
// Use in conditional noexcept
template<typename T>
void wrapper(T value) noexcept(noexcept(T(value))) {
T copy(value);
}
Key: noexcept(expr) is a compile-time operator returning bool. Used in conditional noexcept specifications.
Common Issues
Issue 1: Throwing in noexcept
// ❌ Throws in noexcept
void func() noexcept {
throw std::runtime_error("error"); // std::terminate
}
// ✅ try-catch
void func() noexcept {
try {
riskyOperation();
} catch (...) {
// Handle exception
}
}
Issue 2: std::vector Optimization
class Widget {
public:
// ❌ Without noexcept
Widget(Widget&&) {
// vector reallocation uses copy
}
// ✅ With noexcept
Widget(Widget&&) noexcept {
// vector reallocation uses move
}
};
Impact: Without noexcept, std::vector copies elements on reallocation instead of moving them, causing significant performance degradation for large objects.
Issue 3: Conditional noexcept
template<typename T>
class Wrapper {
public:
// noexcept only if T's move constructor is noexcept
Wrapper(Wrapper&& other)
noexcept(std::is_nothrow_move_constructible_v<T>)
: value(std::move(other.value)) {}
private:
T value;
};
Issue 4: Destructor
// Destructor is implicitly noexcept
class MyClass {
public:
~MyClass() {
// Throwing causes std::terminate
}
};
// Explicit noexcept(false) (not recommended)
class Bad {
public:
~Bad() noexcept(false) {
throw std::runtime_error("error");
}
};
Key: Never throw from destructors. During stack unwinding, a second exception causes terminate.
Optimization
// noexcept enables optimization
void func() noexcept {
// Compiler can omit stack unwinding code
}
// std::vector
std::vector<Widget> vec;
vec.push_back(Widget()); // Uses move if noexcept
Performance impact: noexcept allows compiler to generate simpler, faster code by omitting exception handling machinery.
Production Patterns
Pattern 1: RAII Wrapper
class FileHandle {
FILE* file_;
public:
FileHandle(const char* path) : file_(fopen(path, "r")) {
if (!file_) {
throw std::runtime_error("Failed to open file");
}
}
// noexcept destructor
~FileHandle() noexcept {
if (file_) {
fclose(file_); // Doesn't throw
}
}
// noexcept move
FileHandle(FileHandle&& other) noexcept
: file_(other.file_) {
other.file_ = nullptr;
}
// Prohibit copy
FileHandle(const FileHandle&) = delete;
};
Pattern 2: Exception-Safe swap
class Buffer {
std::vector<char> data_;
public:
// noexcept swap
void swap(Buffer& other) noexcept {
data_.swap(other.data_); // vector::swap is noexcept
}
friend void swap(Buffer& a, Buffer& b) noexcept {
a.swap(b);
}
};
// Usage
Buffer b1, b2;
swap(b1, b2); // Guaranteed no exceptions
Pattern 3: Conditional noexcept Template
template<typename T>
class Wrapper {
T value_;
public:
// noexcept if T's move constructor is noexcept
Wrapper(Wrapper&& other)
noexcept(std::is_nothrow_move_constructible_v<T>)
: value_(std::move(other.value_)) {}
// noexcept if T's swap is noexcept
void swap(Wrapper& other)
noexcept(noexcept(std::swap(value_, other.value_))) {
using std::swap;
swap(value_, other.value_);
}
};
// Usage
Wrapper<std::string> w1, w2;
static_assert(noexcept(w1.swap(w2))); // string::swap is noexcept
FAQ
Q1: When to use noexcept?
A:
- Move operations: Move constructor, move assignment operator
- swap: Swap functions
- Destructor: Always noexcept (implicit)
- Exception-free functions: Functions that don’t throw
class MyClass {
public:
MyClass(MyClass&&) noexcept; // Move constructor
MyClass& operator=(MyClass&&) noexcept; // Move assignment
void swap(MyClass&) noexcept; // swap
~MyClass() noexcept; // Destructor (implicit)
};
Q2: Performance benefits of noexcept?
A:
- Optimization: Compiler omits stack unwinding code
- STL optimization:
std::vectoruses noexcept move operations
class Widget {
public:
// ❌ Without noexcept
Widget(Widget&& other) {
// std::vector uses copy (safe but slow)
}
};
std::vector<Widget> vec;
vec.reserve(100); // Reallocation uses copy
class Widget {
public:
// ✅ With noexcept
Widget(Widget&& other) noexcept {
// std::vector uses move (fast)
}
};
std::vector<Widget> vec;
vec.reserve(100); // Reallocation uses move
Q3: What happens on noexcept violation?
A: std::terminate is called, terminating the program.
void func() noexcept {
throw std::runtime_error("error"); // std::terminate
}
int main() {
func(); // Program terminates
}
Q4: How to use conditional noexcept?
A: Use noexcept(condition). If condition is true, function is noexcept; if false, may throw.
template<typename T>
void func(T value) noexcept(noexcept(T(value))) {
T copy(value); // noexcept if T's copy constructor is noexcept
}
// Or use type_traits
template<typename T>
void func2(T value) noexcept(std::is_nothrow_copy_constructible_v<T>) {
T copy(value);
}
Q5: Are destructors always noexcept?
A: Yes, by default. Unless explicitly specified noexcept(false) (not recommended).
class MyClass {
public:
~MyClass() { // Implicitly noexcept
// Throwing causes std::terminate
}
};
// Explicit noexcept(false) (not recommended)
class Bad {
public:
~Bad() noexcept(false) {
throw std::runtime_error("error");
}
};
Q6: What is the noexcept operator?
A: An operator that checks if an expression is noexcept at compile time, returning bool.
void func() noexcept {}
void func2() {}
static_assert(noexcept(func())); // true
static_assert(!noexcept(func2())); // false
// Use in conditional noexcept
template<typename T>
void wrapper(T value) noexcept(noexcept(T(value))) {
T copy(value);
}
Q7: Is noexcept part of function type?
A: Yes, since C++17. Before C++17, it wasn’t.
// C++17:
void (*ptr1)() noexcept = func; // OK
void (*ptr2)() = func; // Error (type mismatch)
// Function pointer types differ
using FuncNoexcept = void(*)() noexcept;
using Func = void(*)();
Q8: Learning resources for noexcept?
A:
- “Effective Modern C++” by Scott Meyers (Item 14)
- “C++ Concurrency in Action” by Anthony Williams
- cppreference.com - noexcept
Why noexcept Matters for Move Operations
Vector Reallocation Example
#include <vector>
#include <iostream>
class Slow {
std::vector<int> data_;
public:
// ❌ Without noexcept
Slow(Slow&& other) { data_ = std::move(other.data_); }
};
class Fast {
std::vector<int> data_;
public:
// ✅ With noexcept
Fast(Fast&& other) noexcept { data_ = std::move(other.data_); }
};
int main() {
std::vector<Slow> slowVec;
slowVec.reserve(10);
for (int i = 0; i < 100; ++i) {
slowVec.push_back(Slow{}); // Reallocation uses copy!
}
std::vector<Fast> fastVec;
fastVec.reserve(10);
for (int i = 0; i < 100; ++i) {
fastVec.push_back(Fast{}); // Reallocation uses move
}
}
Performance difference: For large objects, noexcept move can be 10-100x faster than copy during reallocation.
Best Practices
1. Always noexcept for Move Operations
Move constructors and move assignment operators should almost always be noexcept.
class MyClass {
public:
MyClass(MyClass&&) noexcept;
MyClass& operator=(MyClass&&) noexcept;
};
2. Always noexcept for swap
void swap(MyClass& a, MyClass& b) noexcept {
using std::swap;
swap(a.member1, b.member1);
swap(a.member2, b.member2);
}
3. Destructors Are Implicitly noexcept
Don’t throw from destructors. If you must handle errors, catch and log them.
~MyClass() noexcept {
try {
cleanup();
} catch (...) {
// Log error but don't rethrow
}
}
4. Use Conditional noexcept for Templates
template<typename T>
class Wrapper {
public:
Wrapper(Wrapper&& other)
noexcept(std::is_nothrow_move_constructible_v<T>)
: value_(std::move(other.value_)) {}
private:
T value_;
};
5. Don’t Overuse noexcept
Only mark functions noexcept if you’re certain they won’t throw. Violations cause terminate, which is worse than an exception.
Production Patterns
Pattern 1: RAII Resource Guard
#include <cstdio>
#include <stdexcept>
class FileHandle {
FILE* file_;
public:
FileHandle(const char* path) : file_(fopen(path, "r")) {
if (!file_) {
throw std::runtime_error("Failed to open file");
}
}
~FileHandle() noexcept {
if (file_) {
fclose(file_); // Doesn't throw
}
}
FileHandle(FileHandle&& other) noexcept
: file_(other.file_) {
other.file_ = nullptr;
}
FileHandle(const FileHandle&) = delete;
FileHandle& operator=(const FileHandle&) = delete;
};
Pattern 2: Exception-Safe Swap
#include <vector>
class Buffer {
std::vector<char> data_;
public:
void swap(Buffer& other) noexcept {
data_.swap(other.data_); // vector::swap is noexcept
}
friend void swap(Buffer& a, Buffer& b) noexcept {
a.swap(b);
}
};
// Usage
Buffer b1, b2;
swap(b1, b2); // Guaranteed no exceptions
Pattern 3: Conditional noexcept Template
#include <type_traits>
#include <utility>
template<typename T>
class Wrapper {
T value_;
public:
// noexcept if T's move constructor is noexcept
Wrapper(Wrapper&& other)
noexcept(std::is_nothrow_move_constructible_v<T>)
: value_(std::move(other.value_)) {}
// noexcept if T's swap is noexcept
void swap(Wrapper& other)
noexcept(noexcept(std::swap(value_, other.value_))) {
using std::swap;
swap(value_, other.value_);
}
};
// Usage
Wrapper<std::string> w1, w2;
static_assert(noexcept(w1.swap(w2))); // string::swap is noexcept
Summary
Key Points
- noexcept: Specifies function doesn’t throw exceptions
- Optimization: Enables compiler optimizations and container move operations
- Move operations: Should almost always be noexcept for performance
- Conditional: Use
noexcept(noexcept(expr))to propagate nothrow guarantee - Violation: Causes
std::terminate, so only use when certain
When to Use noexcept
| Operation | Recommendation | Reason |
|---|---|---|
| Move constructor | ✅ Always | Container optimization |
| Move assignment | ✅ Always | Container optimization |
| Destructor | ✅ Implicit | Stack unwinding safety |
| swap | ✅ Always | Exception safety |
| Regular functions | ⚠️ Carefully | Only if truly nothrow |
noexcept Checklist
- Move constructor marked noexcept?
- Move assignment marked noexcept?
- swap function marked noexcept?
- Destructor doesn’t throw?
- Conditional noexcept for templates?
- Function truly doesn’t throw?
Related Articles
- C++ noexcept Specifier Guide
- C++ Exception Performance
- C++ noexcept Complete Guide | Exception Contracts, Move Optimization, Production Patterns [#42-1]
Keywords
C++ noexcept, exception specifications, move optimization, std::terminate, conditional noexcept, container performance One-line summary: noexcept specifies functions that don’t throw exceptions, enabling compiler optimizations and container move operations. Essential for move constructors and swap functions.
Related Articles (Internal Links)
Other articles related to this topic.
- C++ noexcept 지정자 | ‘예외 명세’ 가이드
- C++ Exception Specifications | ‘예외 명세’ 가이드
- C++ noexcept 완벽 가이드 | 예외 계약·이동 최적화·프로덕션 패턴 [#42-1]
Keywords Covered in This Article (Related Search Terms)
This article covers C++, noexcept, exceptions, C++11, optimization, move semantics.