C++ Exception Safety | "예외 안전성" 가이드
이 글의 핵심
C++ Exception Safety에 대한 실전 가이드입니다. 개념부터 실무 활용까지 예제와 함께 상세히 설명합니다.
예외 안전성이란?
예외 발생 시 프로그램 상태 보장
// ❌ 예외 안전하지 않음
void func() {
int* ptr = new int(10);
process(); // 예외 발생 시 누수
delete ptr;
}
// ✅ 예외 안전
void func() {
auto ptr = std::make_unique<int>(10);
process(); // 예외 발생해도 자동 정리
}
보장 수준
// 1. 기본 보장 (Basic Guarantee)
// - 자원 누수 없음
// - 불변식 유지
// 2. 강한 보장 (Strong Guarantee)
// - 성공 또는 원래 상태
// - 원자적 연산
// 3. nothrow 보장
// - 예외 발생 안함
// - noexcept
실전 예시
예시 1: 기본 보장
class Buffer {
int* data;
size_t size;
public:
void resize(size_t newSize) {
int* newData = new int[newSize]; // 예외 가능
// 복사
size_t copySize = std::min(size, newSize);
std::copy(data, data + copySize, newData);
delete[] data; // 기존 자원 해제
data = newData;
size = newSize;
}
};
예시 2: 강한 보장
class Buffer {
int* data;
size_t size;
public:
void resize(size_t newSize) {
int* newData = new int[newSize];
try {
std::copy(data, data + std::min(size, newSize), newData);
} catch (...) {
delete[] newData; // 실패 시 정리
throw; // 재던지기
}
delete[] data;
data = newData;
size = newSize;
}
};
예시 3: nothrow 보장
class Widget {
public:
// nothrow 보장
void swap(Widget& other) noexcept {
std::swap(data, other.data);
}
// 이동 연산 (noexcept 권장)
Widget(Widget&& other) noexcept
: data(other.data) {
other.data = nullptr;
}
private:
int* data;
};
예시 4: copy-and-swap
class Buffer {
int* data;
size_t size;
public:
Buffer& operator=(const Buffer& other) {
// 강한 보장
Buffer temp(other); // 복사 (예외 가능)
swap(temp); // nothrow
return *this;
}
void swap(Buffer& other) noexcept {
std::swap(data, other.data);
std::swap(size, other.size);
}
};
RAII 활용
class Transaction {
public:
Transaction() {
begin();
}
~Transaction() {
if (!committed) {
rollback(); // 예외 시 롤백
}
}
void commit() {
// ...
committed = true;
}
private:
bool committed = false;
void begin() {}
void rollback() noexcept {}
};
자주 발생하는 문제
문제 1: 부분 변경
// ❌ 부분 변경 후 예외
void update(Data& d) {
d.x = 10;
d.y = compute(); // 예외 발생
d.z = 30; // 실행 안됨
}
// ✅ 임시 객체 사용
void update(Data& d) {
Data temp = d;
temp.x = 10;
temp.y = compute();
temp.z = 30;
d = temp; // 원자적 대입
}
문제 2: 자원 누수
// ❌ 수동 관리
void func() {
Resource* r1 = new Resource();
Resource* r2 = new Resource(); // 예외 시 r1 누수
delete r1;
delete r2;
}
// ✅ RAII
void func() {
auto r1 = std::make_unique<Resource>();
auto r2 = std::make_unique<Resource>();
}
문제 3: 소멸자 예외
// ❌ 소멸자에서 예외
class Bad {
public:
~Bad() {
throw std::runtime_error("에러"); // 위험
}
};
// ✅ noexcept 소멸자
class Good {
public:
~Good() noexcept {
try {
cleanup();
} catch (...) {
// 예외 삼킴
}
}
};
문제 4: 다중 자원
// ❌ 예외 안전하지 않음
void func() {
Resource* r1 = new Resource();
Resource* r2 = new Resource(); // 예외 시 r1 누수
}
// ✅ 순차적 RAII
void func() {
auto r1 = std::make_unique<Resource>();
auto r2 = std::make_unique<Resource>();
}
설계 원칙
// 1. RAII 사용
std::unique_ptr<Resource> resource;
// 2. 소멸자는 noexcept
~MyClass() noexcept {}
// 3. swap은 noexcept
void swap(MyClass& other) noexcept {}
// 4. 이동 연산은 noexcept
MyClass(MyClass&&) noexcept {}
FAQ
Q1: 예외 안전성 수준?
A:
- 기본: 자원 누수 없음
- 강한: 원자적 연산
- nothrow: 예외 없음
Q2: RAII는?
A: 자동 자원 관리. 예외 안전성 핵심.
Q3: 소멸자 예외?
A: 절대 안됨. noexcept.
Q4: 강한 보장은?
A: copy-and-swap 패턴.
Q5: 성능?
A: RAII는 비용 없음. 예외는 비용 있음.
Q6: 예외 안전성 학습 리소스는?
A:
- “Effective C++”
- “C++ Coding Standards”
- “Exception Safety in C++“
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ Stack Unwinding | “스택 되감기” 가이드
- C++ Exception Performance | “예외 성능” 가이드
- C++ noexcept | “예외 명세” 가이드
관련 글
- C++ Stack Unwinding |
- C++ shared_ptr vs unique_ptr |
- C++ malloc vs new vs make_unique | 메모리 할당 완벽 비교
- C++ 복사/이동 생성자 |
- C++ Custom Deleters |