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.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ noexcept 지정자 | ‘예외 명세’ 가이드
- C++ Exception Specifications | ‘예외 명세’ 가이드
- C++ noexcept 완벽 가이드 | 예외 계약·이동 최적화·프로덕션 패턴 [#42-1]
이 글에서 다루는 키워드 (관련 검색어)
C++, noexcept, exceptions, C++11, optimization, move semantics 등으로 검색하시면 이 글이 도움이 됩니다.