C++ noexcept Specifier: Contracts, Moves, and std::terminate
이 글의 핵심
Guide to noexcept: marking non-throwing functions, optimizing moves and swap, and avoiding violations that call std::terminate.
What is noexcept?
It states that a function does not throw.
void func() noexcept {
// must not throw
}
void func2() noexcept(true) {
// same as noexcept
}
void func3() noexcept(false) {
// may throw (default for most functions)
}
Benefits of noexcept
// 1. Compiler optimizations
void swap(int& a, int& b) noexcept {
int temp = a;
a = b;
b = temp;
}
// 2. Move constructors
class MyClass {
public:
MyClass(MyClass&&) noexcept {
// containers prefer noexcept moves
}
};
// 3. Clear contract
int calculate(int x) noexcept {
return x * 2; // no exceptions
}
Conditional noexcept
template<typename T>
void swap(T& a, T& b) noexcept(noexcept(T(std::move(a)))) {
T temp(std::move(a));
a = std::move(b);
b = std::move(temp);
}
// Simpler form
template<typename T>
void swap(T& a, T& b) noexcept(std::is_nothrow_move_constructible<T>::value) {
// ...
}
Practical examples
Example 1: Move constructor
class String {
private:
char* data;
size_t length;
public:
String(const char* str) {
length = strlen(str);
data = new char[length + 1];
strcpy(data, str);
}
~String() {
delete[] data;
}
// Copy ctor (may throw)
String(const String& other) {
length = other.length;
data = new char[length + 1]; // may throw
strcpy(data, other.data);
}
// Move ctor (noexcept)
String(String&& other) noexcept
: data(other.data), length(other.length) {
other.data = nullptr;
other.length = 0;
}
// Move assignment (noexcept)
String& operator=(String&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data;
length = other.length;
other.data = nullptr;
other.length = 0;
}
return *this;
}
};
Example 2: swap
template<typename T>
void swap(T& a, T& b) noexcept {
T temp(std::move(a));
a = std::move(b);
b = std::move(temp);
}
class MyClass {
private:
int* data;
public:
MyClass(int value) : data(new int(value)) {}
~MyClass() {
delete data;
}
friend void swap(MyClass& a, MyClass& b) noexcept {
using std::swap;
swap(a.data, b.data);
}
};
Example 3: Destructor
class Resource {
private:
int* data;
public:
Resource(int value) : data(new int(value)) {}
// destructor is noexcept by default
~Resource() noexcept {
delete data;
// must not throw from destructor
}
};
Example 4: Vector behavior
#include <vector>
class Widget {
public:
Widget() = default;
// Without noexcept: vector may copy on reallocation
Widget(Widget&& other) {
// ...
}
// With noexcept: vector may move on reallocation
Widget(Widget&& other) noexcept {
// ...
}
};
int main() {
std::vector<Widget> vec;
// push_back / resize:
// noexcept move -> use move
// otherwise may copy for exception safety
}
The noexcept operator
// noexcept(expr): is expr noexcept?
void func1() noexcept {}
void func2() {}
int main() {
std::cout << noexcept(func1()) << std::endl; // 1 (true)
std::cout << noexcept(func2()) << std::endl; // 0 (false)
std::cout << noexcept(1 + 2) << std::endl; // 1
std::cout << noexcept(throw 1) << std::endl; // 0
}
Common pitfalls
Pitfall 1: Violating noexcept
void func() noexcept {
throw std::runtime_error("error"); // std::terminate!
}
int main() {
try {
func();
} catch (...) {
// may not run: terminate first
}
}
Pitfall 2: Missing conditional noexcept
// Wrong: always noexcept
template<typename T>
void process(T value) noexcept {
T copy = value; // copy may throw
}
// Better
template<typename T>
void process(T value) noexcept(std::is_nothrow_copy_constructible<T>::value) {
T copy = value;
}
Pitfall 3: Throwing destructor
// Bad
class BadClass {
public:
~BadClass() {
throw std::runtime_error("error"); // dangerous
}
};
// Good
class GoodClass {
public:
~GoodClass() noexcept {
try {
// risky code
} catch (...) {
// swallow or log
}
}
};
Pitfall 4: Move without noexcept
// Without noexcept: vector may copy
class MyClass {
public:
MyClass(MyClass&& other) {
// ...
}
};
// With noexcept: vector may move
class MyClass {
public:
MyClass(MyClass&& other) noexcept {
// ...
}
};
noexcept and performance
#include <vector>
#include <chrono>
class Widget {
int* data;
public:
Widget(int value) : data(new int(value)) {}
~Widget() {
delete data;
}
Widget(Widget&& other)
: data(other.data) {
other.data = nullptr;
}
Widget(Widget&& other) noexcept
: data(other.data) {
other.data = nullptr;
}
};
int main() {
std::vector<Widget> vec;
// noexcept move -> fast path on growth
for (int i = 0; i < 1000000; i++) {
vec.push_back(Widget(i));
}
}
Usage guidelines
// ✅ Good noexcept candidates
// 1. Move ctor / move assign
MyClass(MyClass&&) noexcept;
MyClass& operator=(MyClass&&) noexcept;
// 2. swap
void swap(MyClass&, MyClass&) noexcept;
// 3. Destructor (implicitly noexcept)
~MyClass() noexcept;
// 4. Simple getters
int getValue() const noexcept;
// ❌ Avoid noexcept when
// 1. Function may throw
void process() {
// may throw
}
// 2. Future may add throws
void complexOperation() {
// evolving API
}
FAQ
Q1: When to use noexcept?
A:
- Move operations
swap- Destructors
- Functions that truly never throw
Q2: What if you violate noexcept?
A: std::terminate—program ends.
Q3: Performance benefits?
A:
- Fewer unwind paths
vectorcan move elements- Stronger optimization hints
Q4: Conditional noexcept?
A: noexcept(expression)—especially in templates.
Q5: Destructors?
A: Implicitly noexcept; do not throw.
Q6: Learning resources?
A:
- Effective Modern C++
- cppreference.com
- C++ Primer
Related posts (internal links)
- noexcept overview
- Exception specifications
- Move constructor
Practical tips
Debugging
- Warnings first, minimal repro
Performance
- Profile before optimizing
Code review
- Team conventions
Practical checklist
Before coding
- Right approach?
- Maintainable?
- Performance OK?
While coding
- Warnings clean?
- Edge cases?
- Error handling?
At review
- Intent clear?
- Tests?
- Docs?
Keywords
C++, noexcept, exceptions, move semantics, C++11
Related posts
- noexcept keyword
- noexcept deep dive (series)
- async & launch
- Atomic operations
- Attributes