[2026] C++ 예외 처리 | try/catch/throw 완벽 정리 [에러 처리]

[2026] C++ 예외 처리 | try/catch/throw 완벽 정리 [에러 처리]

이 글의 핵심

C++ 예외 처리의 핵심 개념과 실무 포인트를 정리합니다.

기본 예외 처리

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <iostream>
#include <stdexcept>
using namespace std;
int divide(int a, int b) {
    if (b == 0) {
        throw runtime_error("0으로 나눌 수 없습니다");
    }
    return a / b;
}
int main() {
    try {
        int result = divide(10, 0);
        cout << result << endl;
    } catch (runtime_error& e) {
        cout << "에러: " << e.what() << endl;
    }
    
    return 0;
}

표준 예외 클래스

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <exception>
#include <stdexcept>
// 기본 예외
exception
// 논리 에러
logic_error
  ├─ invalid_argument
  ├─ domain_error
  ├─ length_error
  └─ out_of_range
// 런타임 에러
runtime_error
  ├─ range_error
  ├─ overflow_error
  └─ underflow_error

여러 예외 처리

아래 코드는 cpp를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

try {
    // 예외 발생 가능한 코드
} catch (out_of_range& e) {
    cout << "범위 초과: " << e.what() << endl;
} catch (invalid_argument& e) {
    cout << "잘못된 인자: " << e.what() << endl;
} catch (exception& e) {
    cout << "기타 에러: " << e.what() << endl;
} catch (...) {
    cout << "알 수 없는 에러" << endl;
}

실전 예시

예시 1: 파일 처리 예외

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <fstream>
#include <iostream>
#include <stdexcept>
using namespace std;
string readFile(const string& filename) {
    ifstream file(filename);
    
    if (!file.is_open()) {
        throw runtime_error("파일을 열 수 없습니다: " + filename);
    }
    
    string content, line;
    while (getline(file, line)) {
        content += line + "\n";
    }
    
    return content;
}
int main() {
    try {
        string content = readFile("config.txt");
        cout << content << endl;
    } catch (runtime_error& e) {
        cerr << "에러: " << e.what() << endl;
        return 1;
    }
    
    return 0;
}

설명: 파일 처리 시 예외를 던져서 에러를 명확하게 처리합니다.

예시 2: 커스텀 예외 클래스

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <iostream>
#include <exception>
#include <string>
using namespace std;
class InvalidAgeException : public exception {
private:
    string message;
    
public:
    InvalidAgeException(int age) {
        message = "잘못된 나이: " + to_string(age);
    }
    
    const char* what() const noexcept override {
        return message.c_str();
    }
};
class Person {
private:
    string name;
    int age;
    
public:
    Person(string n, int a) : name(n) {
        if (a < 0 || a > 150) {
            throw InvalidAgeException(a);
        }
        age = a;
    }
    
    void print() {
        cout << name << ", " << age << "살" << endl;
    }
};
int main() {
    try {
        Person p1("Alice", 25);
        p1.print();
        
        Person p2("Bob", 200);  // 예외 발생
        p2.print();
    } catch (InvalidAgeException& e) {
        cerr << "에러: " << e.what() << endl;
    }
    
    return 0;
}

설명: 도메인 특화 예외를 만들어 에러를 더 명확하게 표현합니다.

예시 3: RAII와 예외 안전성

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <iostream>
#include <memory>
using namespace std;
class Resource {
public:
    Resource() { cout << "리소스 할당" << endl; }
    ~Resource() { cout << "리소스 해제" << endl; }
    void use() { cout << "리소스 사용" << endl; }
};
void dangerousFunction() {
    throw runtime_error("에러 발생!");
}
int main() {
    try {
        // ❌ 수동 관리 (예외 시 누수)
        // Resource* r = new Resource();
        // dangerousFunction();
        // delete r;  // 실행 안됨!
        
        // ✅ RAII (자동 해제)
        unique_ptr<Resource> r = make_unique<Resource>();
        r->use();
        dangerousFunction();
        // 예외 발생해도 자동으로 해제됨
        
    } catch (exception& e) {
        cout << "에러: " << e.what() << endl;
    }
    
    return 0;
}

설명: 스마트 포인터를 사용하면 예외 발생 시에도 리소스가 안전하게 해제됩니다.

자주 발생하는 문제

문제 1: 예외를 값으로 캐치

증상: 예외 객체가 잘림 (slicing) 원인: 값으로 캐치하면 파생 클래스 정보 손실 해결법: 다음은 cpp를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ 잘못된 코드
try {
    throw runtime_error("에러");
} catch (exception e) {  // 값으로 캐치
    // 파생 클래스 정보 손실
}
// ✅ 올바른 코드
try {
    throw runtime_error("에러");
} catch (exception& e) {  // 참조로 캐치
    cout << e.what() << endl;
}
// ✅ const 참조 (더 안전)
catch (const exception& e) {
    cout << e.what() << endl;
}

문제 2: 소멸자에서 예외 던지기

증상: 프로그램 비정상 종료 원인: 소멸자에서 예외가 발생하면 terminate() 호출 해결법: 다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ 위험한 코드
class Resource {
public:
    ~Resource() {
        throw runtime_error("에러");  // 절대 안됨!
    }
};
// ✅ 올바른 코드
class Resource {
public:
    ~Resource() noexcept {
        try {
            // 위험한 작업
        } catch (...) {
            // 예외 삼킴
        }
    }
};

문제 3: 예외 명세 (exception specification)

증상: throw() 사용 시 경고 또는 에러 원인: throw()는 C++11에서 deprecated 해결법: 아래 코드는 cpp를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ 구식 (deprecated)
void func() throw(int, runtime_error) {
    // ...
}
// ✅ noexcept 사용
void func() noexcept {  // 예외 안던짐
    // ...
}
void func2() noexcept(false) {  // 예외 던질 수 있음
    // ...
}

try-catch 기본 정리

  • try: 예외가 날 수 있는 코드 블록.
  • throw: throw 표현식의 타입이 첫 번째로 타입이 일치하는 catch로 전달됩니다. 파생 클래스를 잡으려면 기본 클래스보다 앞에 둡니다(아니면 slicing·놓침).
  • catch (…): 타입을 모를 때; 보통 마지막에 두고, 로깅 후 std::terminate를 피하기 위해 재던지기 여부를 신중히 결정합니다. 아래 코드는 cpp를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
try {
    mayThrow();
} catch (const std::invalid_argument& e) {
    // 구체적 처리
} catch (const std::exception& e) {
    // 표준 예외 공통
} catch (...) {
    // 알 수 없는 예외
}

참조로 잡기: catch (const std::exception& e)가 값 복사보다 안전하고 효율적입니다.

예외 vs 에러 코드

측면예외에러 코드(expected, optional, bool 등)
제어 흐름실패가 드물 때 깔끔실패가 흔한 API에 적합
성능예외 경로는 상대적으로 비용 큼핫 루프에서 예측 가능
C 연동C API와 섞기 어려움errno·리턴 코드와 잘 맞음
가시성함수 시그니처만 보면 던짐 여부가 불명확할 수 있음(C++17까지)호출부에서 실패 처리 강제하기 쉬움
C++23의 std::expected<T, E>는 “값 또는 에러”를 타입으로 표현해, 예외 없이도 실패를 명시적으로 전달할 수 있습니다. 팀 규칙으로 “예외는 정말 예외적인 실패만”으로 한정하면 혼선이 줄어듭니다.

RAII와 예외 안전성

RAII: 자원은 생성자에서 잡고 소멸자에서 놓습니다. 예외가 나도 스택 풀기로 소멸자가 호출되므로 누수를 막습니다. 예외 안전성 보장의 전형적인 세 단계는 다음과 같습니다.

  • 기본 보장(basic): 예외가 나도 리소스 누수 없이 유효한(일관성이 깨질 수는 있는) 상태로 남습니다.
  • 강한 보장(strong): 실패 시 상태가 변하지 않음(트랜잭션·복사-교체 관용구와 연결).
  • 끊김 없는 보장(nothrow): 연산이 예외를 던지지 않음(noexcept로 표현 가능). 실무에서는 vector::push_back이 실패하면 무엇이 보장되는지처럼 표준 컨테이너의 예외 보장을 알아두면 설계가 쉬워집니다. 커스텀 클래스도 복사 대입·이동 대입이 예외를 낼 때 클래스 불변식이 어떻게 되는지 문서화하는 것이 좋습니다.

noexcept 심화

  • 의미: “이 함수는 예외를 밖으로 던지지 않는다.” 위반 시 std::terminate로 이어질 수 있습니다.
  • 이동 연산: std::vector가 재할당 시 이동을 쓰려면 이동이 noexcept인 경우가 많아, 이동 생성자/대입에 noexcept를 붙이는 것이 성능에 직결됩니다.
  • 소멸자: 소멸자는 암시적으로 noexcept입니다. 소멸자에서 예외를 던지면 안 됩니다.
void swap(MyType& a, MyType& b) noexcept {
    // 멤버 swap만 호출하고 예외 없음을 보장할 때
}

noexcept(expr) 형태로 조건부로 지정할 수도 있습니다.

실전 패턴

  1. 생성자 실패: 생성자에서는 반환값이 없으므로 유효하지 않은 상태를 두기 어렵다면 예외(또는 별도 팩토리 + expected)를 씁니다.
  2. 경계에서만 catch: 라이브러리 내부는 예외를 전파하고, UI·main 근처에서 로깅·복구합니다.
  3. std::nested_exception: 저수준에서 잡은 예외를 감싸 상위로 올릴 때 원인 보존.
  4. 테스트: REQUIRE_THROWS_AS (Catch2 등)로 예외 타입을 고정합니다.
  5. 신규 코드 가이드: 핫 루프는 에러 코드, I/O·파싱 실패는 예외처럼 팀 컨벤션을 한 가지로 정합니다.

FAQ

Q1: 예외 처리는 느린가요?

A: 예외가 발생하지 않으면 거의 오버헤드가 없습니다. 예외 발생 시에만 느립니다.

Q2: 언제 예외를 사용해야 하나요?

A:

  • 복구 불가능한 에러
  • 생성자 실패
  • 깊은 호출 스택에서 에러 전파 사용하지 말아야 할 때:
  • 정상적인 제어 흐름
  • 성능이 중요한 루프 내부

Q3: return vs throw?

A:

  • return: 정상 종료, 예상된 결과
  • throw: 비정상 상황, 에러

Q4: 모든 예외를 잡아야 하나요?

A: 처리할 수 있는 예외만 잡으세요. 처리 못하면 상위로 전파하는 것이 좋습니다.

Q5: noexcept는 왜 사용하나요?

A:

  • 컴파일러 최적화 가능
  • move 생성자에 필수 (vector 등에서 move 사용)
  • 의도 명확화

Q6: 예외 vs 에러 코드?

A:

  • 예외: 깔끔한 코드, 에러 전파 쉬움
  • 에러 코드: 성능 중요, C 호환 필요

같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.

관련 글

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3