C++ Exception Specifications: noexcept, History, and throw() Removal

C++ Exception Specifications: noexcept, History, and throw() Removal

이 글의 핵심

Practical guide to C++ exception specifications: noexcept basics, history of throw(), move/swap patterns, and destructor rules.

Introduction

Exception specifications describe what exceptions a function may throw.


1. History of exception specifications

C++03 — throw()

// No exceptions
void func() throw();

// Specific types
void func() throw(std::exception);
void func() throw(int, double);

// Removed in C++17

C++11 — noexcept

// No exceptions
void func() noexcept;

// Conditional
void func() noexcept(true);   // noexcept
void func() noexcept(false);  // may throw

2. noexcept basics

Basic usage

#include <iostream>

void safe_func() noexcept {
    std::cout << "no throw" << std::endl;
}

void risky_func() {
    throw std::runtime_error("error");
}

int main() {
    safe_func();
    
    try {
        risky_func();
    } catch (const std::exception& e) {
        std::cout << e.what() << std::endl;
    }
    
    return 0;
}

Checking noexcept

#include <iostream>
#include <type_traits>

void func1() noexcept {}
void func2() {}

int main() {
    std::cout << std::boolalpha;
    std::cout << "func1: " << noexcept(func1()) << std::endl;  // true
    std::cout << "func2: " << noexcept(func2()) << std::endl;  // false
    
    return 0;
}

3. Move operations and noexcept

Move constructor

#include <vector>
#include <iostream>

class Widget {
public:
    Widget() = default;
    
    Widget(Widget&& other) noexcept
        : data(std::move(other.data)) {
        std::cout << "move ctor" << std::endl;
    }
    
    Widget(const Widget& other)
        : data(other.data) {
        std::cout << "copy ctor" << std::endl;
    }
    
private:
    std::vector<int> data{1, 2, 3};
};

int main() {
    std::vector<Widget> vec;
    vec.reserve(10);
    
    Widget w;
    vec.push_back(std::move(w));  // move ctor (noexcept)
    
    return 0;
}

Important: std::vector often requires noexcept move constructors to move elements during reallocation.


4. swap and noexcept

swap implementation

class Data {
public:
    Data(int v) : value(v) {}
    
    void swap(Data& other) noexcept {
        std::swap(value, other.value);
    }
    
    int getValue() const noexcept { return value; }
    
private:
    int value;
};

namespace std {
    template<>
    void swap(Data& lhs, Data& rhs) noexcept {
        lhs.swap(rhs);
    }
}

int main() {
    Data d1(10), d2(20);
    std::swap(d1, d2);
    
    std::cout << d1.getValue() << std::endl;  // 20
    std::cout << d2.getValue() << std::endl;  // 10
    
    return 0;
}

5. Conditional noexcept

Templates and noexcept

template<typename T>
class Container {
public:
    Container(Container&& other) 
        noexcept(std::is_nothrow_move_constructible_v<T>)
        : data(std::move(other.data)) {}
    
    void clear() noexcept(std::is_nothrow_destructible_v<T>) {
        data.clear();
    }
    
private:
    std::vector<T> data;
};

6. Destructors and noexcept

Implicit noexcept

class MyClass {
public:
    // destructor implicitly noexcept unless a member’s dtor can throw
    ~MyClass() {
        // throwing here -> std::terminate during stack unwind
    }
};

class Explicit {
public:
    ~Explicit() noexcept {
        // still must not throw
    }
};

Exceptions in destructor cleanup

class SafeClass {
public:
    ~SafeClass() noexcept {
        try {
            cleanup();
        } catch (const std::exception& e) {
            std::cerr << "cleanup error: " << e.what() << std::endl;
        }
    }
    
private:
    void cleanup() {
        // may throw
    }
};

7. Practical example: resource management

#include <iostream>
#include <memory>

class Resource {
public:
    Resource() {
        std::cout << "allocate" << std::endl;
    }
    
    ~Resource() noexcept {
        std::cout << "release" << std::endl;
    }
    
    void use() noexcept {
        std::cout << "use" << std::endl;
    }
};

class Manager {
public:
    Manager() : res(std::make_unique<Resource>()) {}
    
    Manager(Manager&& other) noexcept
        : res(std::move(other.res)) {}
    
    Manager& operator=(Manager&& other) noexcept {
        res = std::move(other.res);
        return *this;
    }
    
    void process() noexcept {
        if (res) {
            res->use();
        }
    }
    
private:
    std::unique_ptr<Resource> res;
};

int main() {
    Manager m1;
    m1.process();
    
    Manager m2 = std::move(m1);
    m2.process();
    
    return 0;
}

Summary

Key points

  1. noexcept: C++11 exception contract
  2. throw(): deprecated (removed in C++17)
  3. Move operations: mark noexcept when safe
  4. Destructors: implicitly noexcept in normal cases
  5. Conditional: noexcept(expression)

When to use noexcept

Functionnoexcept?Reason
Move ctorYesContainer performance
Move assignYesContainer performance
swapYesException safety
DestructorYes (default)Avoid terminate during unwind
Simple gettersOftenNo throws
Complex logicMaybe notMay throw

Next steps

  • C++ noexcept
  • Exception handling
  • Move semantics

  • Exception handling
  • noexcept specifier
  • noexcept keyword
  • Exception basics (series)
  • Exception safety (series)