C++ Exception Safety | "예외 안전성" 가이드

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 |