C++ noexcept Specifier: Contracts, Moves, and std::terminate

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
  • vector can 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

  • 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


  • noexcept keyword
  • noexcept deep dive (series)
  • async & launch
  • Atomic operations
  • Attributes