본문으로 건너뛰기 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.


Other articles related to this topic.


Keywords Covered in This Article (Related Search Terms)

This article covers C++, noexcept, exceptions, C++11, optimization, move semantics.