본문으로 건너뛰기
Previous
Next
C++ noexcept Complete Guide

C++ noexcept Complete Guide

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::vector checks 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()

Featurenoexcept (C++11)throw() (C++98, deprecated)
On exceptionstd::terminatestd::unexpectedstd::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::vector uses 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:


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

  1. noexcept: Specifies function doesn’t throw exceptions
  2. Optimization: Enables compiler optimizations and container move operations
  3. Move operations: Should almost always be noexcept for performance
  4. Conditional: Use noexcept(noexcept(expr)) to propagate nothrow guarantee
  5. Violation: Causes std::terminate, so only use when certain

When to Use noexcept

OperationRecommendationReason
Move constructor✅ AlwaysContainer optimization
Move assignment✅ AlwaysContainer optimization
Destructor✅ ImplicitStack unwinding safety
swap✅ AlwaysException safety
Regular functions⚠️ CarefullyOnly 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?

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, exceptions, C++11, optimization, move semantics 등으로 검색하시면 이 글이 도움이 됩니다.