본문으로 건너뛰기 C++ Exception Specifications: noexcept, History, and throw

C++ Exception Specifications: noexcept, History, and throw

C++ Exception Specifications: noexcept, History, and throw

이 글의 핵심

C++ exception specifications from throw() to noexcept: move operations, swap, destructors, conditional noexcept, and why dynamic specs were removed.

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



Frequently Asked Questions (FAQ)

Q. When would I use this in practice?

A. C++ exception specifications from throw() to noexcept: move operations, swap, destructors, conditional noexcept, and why…

Q. What should I read before this?

A. Follow the previous article or related articles links at the bottom of each post to learn in sequence. See the C++ series index for the full picture.

Q. Where can I study this more deeply?

A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.


Other articles related to this topic.


Keywords Covered in This Article (Related Search Terms)

This article covers C++, Exception Specifications, noexcept, throw, Deprecated.