예외 처리

예외 처리 (Exception Handling)

예외 처리는 프로그램 실행의 한 지점에서, 이전에 지나간 지점과 연관된 핸들러로 제어와 정보를 넘기는 방법이에요. 다시 말해 예외 처리는 호출 스택을 따라 위로 제어를 옮기죠.

출처: cppreference

본문

throw 표현식을 평가하면 예외가 던져져요. 예외는 다른 맥락에서도 던져질 수 있어요.

예외가 잡히기 위해서는 throw 표현식이 try 블록 안에 있어야 하고, try 블록이 예외 객체의 타입과 일치하는 핸들러를 포함해야 해요.

함수를 선언할 때, 그 함수가 던질 수 있는 예외의 종류를 제한하는 다음 사양을 제공할 수 있어요:

  • 동적 예외 사양 (C++17 이전)
  • noexcept 사양 (C++11 이후)

예외 처리 중 발생하는 오류는 std::terminatestd::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::sortCompare 함수 객체, std::make_sharedT 생성자)에서 예외가 던져지면, 변경 없이 그대로 호출자에게 전파돼요.

예외 객체 (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)