[2026] C++ Exception Specifications | 예외 명세 가이드
이 글의 핵심
C++ Exception Specifications: 예외 명세 가이드. 예외 명세 역사·noexcept 기본.
들어가며
예외 명세는 함수가 던질 수 있는 예외를 지정하는 기능입니다.
실무에서 마주한 현실
개발을 배울 때는 모든 게 깔끔하고 이론적입니다. 하지만 실무는 다릅니다. 레거시 코드와 씨름하고, 급한 일정에 쫓기고, 예상치 못한 버그와 마주합니다. 이 글에서 다루는 내용도 처음엔 이론으로 배웠지만, 실제 프로젝트에 적용하면서 “아, 이래서 이렇게 설계하는구나” 하고 깨달은 것들입니다. 특히 기억에 남는 건 첫 프로젝트에서 겪은 시행착오입니다. 책에서 배운 대로 했는데 왜 안 되는지 몰라 며칠을 헤맸죠. 결국 선배 개발자의 코드 리뷰를 통해 문제를 발견했고, 그 과정에서 많은 걸 배웠습니다. 이 글에서는 이론뿐 아니라 실전에서 마주칠 수 있는 함정들과 해결 방법을 함께 다루겠습니다.
1. 예외 명세 역사
C++03 - throw()
아래 코드는 cpp를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// 예외 없음
void func() throw();
// 특정 예외만
void func() throw(std::exception);
void func() throw(int, double);
// C++17에서 제거됨
C++11 - noexcept
아래 코드는 cpp를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// 예외 없음
void func() noexcept;
// 조건부
void func() noexcept(true); // noexcept
void func() noexcept(false); // 예외 가능
2. noexcept 기본
기본 사용
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#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 검사
아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#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
이동 생성자
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#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 구현
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class MyClass {
public:
// 소멸자는 암시적으로 noexcept
~MyClass() {
// 예외 던지면 std::terminate
}
};
class Explicit {
public:
// 명시적 noexcept
~Explicit() noexcept {
// 예외 던지면 std::terminate
}
};
소멸자에서 예외 처리
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class SafeClass {
public:
~SafeClass() noexcept {
try {
cleanup();
} catch (const std::exception& e) {
std::cerr << "정리 중 에러: " << e.what() << std::endl;
}
}
private:
void cleanup() {
// 예외 가능
}
};
7. 실전 예제
예제 1: 안전한 리소스 관리
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#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 | ✅ | 예외 없음 |
| 복잡한 로직 | ❌ | 예외 가능 |