예외 처리
예외 처리 (Exception Handling)
예외 처리는 프로그램 실행의 한 지점에서, 이전에 지나간 지점과 연관된 핸들러로 제어와 정보를 넘기는 방법이에요. 다시 말해 예외 처리는 호출 스택을 따라 위로 제어를 옮기죠.
출처: cppreference
본문
throw 표현식을 평가하면 예외가 던져져요. 예외는 다른 맥락에서도 던져질 수 있어요.
예외가 잡히기 위해서는 throw 표현식이 try 블록 안에 있어야 하고, try 블록이 예외 객체의 타입과 일치하는 핸들러를 포함해야 해요.
함수를 선언할 때, 그 함수가 던질 수 있는 예외의 종류를 제한하는 다음 사양을 제공할 수 있어요:
- 동적 예외 사양 (C++17 이전)
- noexcept 사양 (C++11 이후)
예외 처리 중 발생하는 오류는 std::terminate와 std::unexpected(C++17 이전)로 다뤄져요.
용도 (Usage)
throw 표현식은 실행 스택 위의 임의의 코드 블록으로 제어를 넘기는 데 쓸 수 있어요(std::longjmp와 비슷하게). 하지만 의도된 용도는 오류 처리예요.
오류 처리 (Error handling)
예외를 던지는 것은 함수에서 오류를 신호하기 위해 사용돼요. 여기서 "오류"는 보통 다음으로 제한돼요:
- 사후 조건을 충족하지 못한 실패, 예를 들어 유효한 반환 값 객체를 만들지 못한 경우.
- 호출해야 하는 다른 함수의 사전 조건을 충족하지 못한 실패.
- (비-private 멤버 함수의 경우) 클래스 불변식(class invariant)을 (재)확립하지 못한 실패.
특히 이는 생성자의 실패(RAII 참고)와 대부분의 연산자의 실패는 예외를 던져 보고해야 한다는 뜻이에요.
게다가, 이른바 넓은 계약(wide contract) 함수는 허용할 수 없는 입력을 나타내는 데 예외를 써요. 예를 들어 std::basic_string::at는 사전 조건이 없지만, 인덱스가 범위를 벗어나면 예외를 던져 나타내죠.
예외 안전성 (Exception safety)
함수가 오류 조건을 보고한 후, 프로그램 상태에 대해 추가적인 보장을 제공할 수 있어요. 일반적으로 인정되는 네 가지 수준의 예외 보장이 있어요. 이들은 서로의 엄격한 상위 집합이에요:
- Nothrow(또는 nofail) 예외 보장 — 함수가 절대 예외를 던지지 않아요. Nothrow(오류를 다른 수단으로 보고하거나 숨김)는 소멸자와 스택 풀기 중에 호출될 수 있는 다른 함수에서 기대돼요. 소멸자는 기본적으로
noexcept예요. (C++11 이후) Nofail(함수가 항상 성공)은 swap, 이동 생성자, 그리고 강한 예외 보장을 제공하는 함수들이 쓰는 다른 함수에서 기대돼요. - 강한 예외 보장 (Strong exception guarantee) — 함수가 예외를 던지면, 프로그램 상태가 함수 호출 직전 상태로 되돌려져요 (예:
std::vector::push_back). - 기본 예외 보장 (Basic exception guarantee) — 함수가 예외를 던지면, 프로그램은 유효한 상태에 있어요. 자원이 누출되지 않고 모든 객체의 불변식이 온전해요.
- 예외 보장 없음 (No exception guarantee) — 함수가 예외를 던지면, 프로그램이 유효한 상태가 아닐 수 있어요. 자원 누출, 메모리 손상, 또는 불변식을 파괴하는 다른 오류가 발생했을 수 있어요.
제네릭 컴포넌트는 추가로 **예외 중립 보장(exception-neutral guarantee)**을 제공할 수 있어요. 템플릿 매개변수(예: std::sort의 Compare 함수 객체, std::make_shared의 T 생성자)에서 예외가 던져지면, 변경 없이 그대로 호출자에게 전파돼요.
예외 객체 (Exception objects)
어떤 완전 타입의 객체와 void에 대한 cv 포인터는 예외 객체로 던져질 수 있지만, 모든 표준 라이브러리 함수는 이름 없는 객체를 값으로 던져요. 그 객체들의 타입은 (직접 또는 간접적으로) std::exception에서 파생돼요. 사용자 정의 예외도 보통 이 패턴을 따라요.
예외 객체의 불필요한 복사와 객체 슬라이싱을 피하기 위해, 핸들러는 참조로 잡는 것이 모범 사례예요.
Notes
| 기능 테스트 매크로 | 값 | 표준 | 기능 |
|---|---|---|---|
__cpp_constexpr_exceptions |
202411L |
(C++26) | constexpr 예외 |
외부 링크
- H. Sutter (2004) "When and How to Use Exceptions" in Dr. Dobb's
- H. Sutter, A. Alexandrescu (2004), "C++ Coding Standards", Item 70
- C++ Core Guidelines I.10: Use exceptions to signal a failure to perform a required task
- B. Stroustrup (2000), "The C++ Programming Language" Appendix E
- H. Sutter (2000) "Exceptional C++"
- D. Abrahams (2001) "Exception Safety in Generic Components"
- D. Abrahams (2001) "Error and Exception Handling"
- isocpp.org Super-FAQ "What should I throw?"
- C++ Core Guidelines E.14: Use purpose-designed user-defined types as exceptions (not built-in types)
- C++ Core Guidelines E.15: Throw by value, catch exceptions from a hierarchy by reference
- S. Meyers (1996) "More Effective C++" Item 13
- isocpp.org Super-FAQ "What should I catch?"
- H. Sutter, A. Alexandrescu (2004) "C++ Coding Standards" Item 73
더 알아보기 (Learn more)
- 예외 사양 — 함수가 던질 수 있는 예외 목록
- noexcept 지정자 (C++11)
- throw 표현식 — 예외를 던지는 표현식
- try/catch 블록 — 예외를 잡는 블록