C++ Exception Specifications | "예외 명세" 가이드

C++ Exception Specifications | "예외 명세" 가이드

이 글의 핵심

C++ Exception Specifications에 대한 실전 가이드입니다. 개념부터 실무 활용까지 예제와 함께 상세히 설명합니다.

들어가며

예외 명세는 함수가 던질 수 있는 예외를 지정하는 기능입니다.


1. 예외 명세 역사

C++03 - throw()

// 예외 없음
void func() throw();

// 특정 예외만
void func() throw(std::exception);
void func() throw(int, double);

// C++17에서 제거됨

C++11 - noexcept

// 예외 없음
void func() noexcept;

// 조건부
void func() noexcept(true);   // noexcept
void func() noexcept(false);  // 예외 가능

2. noexcept 기본

기본 사용

#include <iostream>

void safe_func() noexcept {
    std::cout << "예외 없음" << std::endl;
}

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

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

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. 이동 연산과 noexcept

이동 생성자

#include <vector>
#include <iostream>

class Widget {
public:
    Widget() = default;
    
    // 이동 생성자 (noexcept 권장)
    Widget(Widget&& other) noexcept
        : data(std::move(other.data)) {
        std::cout << "이동 생성" << std::endl;
    }
    
    // 복사 생성자
    Widget(const Widget& other)
        : data(other.data) {
        std::cout << "복사 생성" << 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));  // 이동 생성 (noexcept)
    
    return 0;
}

중요: std::vector는 이동 생성자가 noexcept일 때만 이동을 사용합니다.


4. swap과 noexcept

swap 구현

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. 조건부 noexcept

템플릿과 noexcept

template<typename T>
class Container {
public:
    // T의 이동 생성자가 noexcept면 noexcept
    Container(Container&& other) 
        noexcept(std::is_nothrow_move_constructible_v<T>)
        : data(std::move(other.data)) {}
    
    // T의 소멸자가 noexcept면 noexcept
    void clear() noexcept(std::is_nothrow_destructible_v<T>) {
        data.clear();
    }
    
private:
    std::vector<T> data;
};

6. 소멸자와 noexcept

암시적 noexcept

class MyClass {
public:
    // 소멸자는 암시적으로 noexcept
    ~MyClass() {
        // 예외 던지면 std::terminate
    }
};

class Explicit {
public:
    // 명시적 noexcept
    ~Explicit() noexcept {
        // 예외 던지면 std::terminate
    }
};

소멸자에서 예외 처리

class SafeClass {
public:
    ~SafeClass() noexcept {
        try {
            cleanup();
        } catch (const std::exception& e) {
            std::cerr << "정리 중 에러: " << e.what() << std::endl;
        }
    }
    
private:
    void cleanup() {
        // 예외 가능
    }
};

7. 실전 예제

예제 1: 안전한 리소스 관리

#include <iostream>
#include <memory>

class Resource {
public:
    Resource() {
        std::cout << "리소스 할당" << std::endl;
    }
    
    ~Resource() noexcept {
        std::cout << "리소스 해제" << std::endl;
    }
    
    void use() noexcept {
        std::cout << "리소스 사용" << 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;
}

정리

핵심 요약

  1. noexcept: 예외 없음 명세 (C++11)
  2. throw(): deprecated (C++17 제거)
  3. 이동 연산: noexcept 권장
  4. 소멸자: 암시적 noexcept
  5. 조건부: noexcept(expression)

noexcept 사용 권장

함수noexcept이유
이동 생성자성능 최적화
이동 대입성능 최적화
swap예외 안전성
소멸자암시적 noexcept
간단한 getter예외 없음
복잡한 로직예외 가능

다음 단계

  • C++ noexcept
  • C++ Exception Handling
  • C++ Move Semantics

관련 글

  • C++ 예외 처리 | try/catch/throw
  • C++ noexcept 지정자 |
  • C++ noexcept 키워드 |
  • C++ 예외 처리 | try-catch-throw와 예외 vs 에러 코드, 언제 뭘 쓸지
  • C++ 예외 안전성 |