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;
}
정리
핵심 요약
- noexcept: 예외 없음 명세 (C++11)
- throw(): deprecated (C++17 제거)
- 이동 연산: noexcept 권장
- 소멸자: 암시적 noexcept
- 조건부:
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++ 예외 안전성 |