Handling exceptions
예외 는 핸들러에 의해 처리될 수 있습니다.
목차 |
핸들러
catch
(
attr
(선택적)
type-specifier-seq
declarator
)
compound-statement
|
(1) | ||||||||
catch
(
attr
(선택적)
type-specifier-seq
abstract-declarator
(선택적)
)
compound-statement
|
(2) | ||||||||
catch
(
...
)
compound-statement
|
(3) | ||||||||
| attr | - | (since C++11) 임의의 개수의 attributes , 매개변수에 적용됨 |
| type-specifier-seq | - | 형식 매개변수 선언의 일부, 함수 parameter list 와 동일 |
| declarator | - | 매개변수 선언의 일부, 함수 parameter list 와 동일 |
| abstract-declarator | - | 이름 없는 매개변수 선언의 일부, 함수 parameter list 와 동일 |
| compound-statement | - | 하나의 compound statement |
핸들러의 매개변수 선언은 해당 핸들러가 진입될 수 있는 예외의 유형을 설명합니다.
매개변수가 다음 유형 중 하나로 선언된 경우 프로그램의 형식이 올바르지 않습니다:
- 불완전한 타입 incomplete type
- 추상 클래스 타입 abstract class type
| (C++11부터) |
- (cv 한정자가 있을 수 있는) void 를 제외한 불완전 타입에 대한 포인터
- 불완전 타입에 대한 lvalue 참조
매개변수가 "T의 배열" 또는 함수 타입
T
로 선언된 경우, 해당 타입은 "T에 대한 포인터"로 조정됩니다.
매개변수 타입이
T
인 핸들러는 "타입
T
의 핸들러"로 축약하여 표현할 수 있습니다.
예외 매칭
각 try 블록은 여러 개의 핸들러와 연결되며, 이러한 핸들러들은 핸들러 시퀀스를 형성합니다. 예외가 try 블록에서 발생하면, 시퀀스 내의 핸들러들은 예외와 일치하는지 확인하기 위해 나타난 순서대로 시도됩니다.
핸들러는 다음 조건 중 하나라도 충족될 때
exception object
의 타입
E
와 매치됩니다:
-
핸들러는 "cv 한정자가 있을 수 있는
T" 타입이거나 "cv 한정자가 있을 수 있는T에 대한 lvalue 참조" 타입이며, 다음 조건 중 하나를 만족합니다:
-
-
E와T가 동일한 타입입니다(최상위 cv 한정자는 무시). -
T가E의 명확한(ambiguous하지 않은) public 기본 클래스입니다.
-
-
핸들러는 "cv 한정자가 있을 수 있는
T" 타입이거나 const T & 타입입니다. 여기서T는 포인터 또는 포인터-대-멤버 타입이며, 다음 조건 중 하나가 충족됩니다:
-
-
E는 다음 변환 중 적어도 하나를 통해T로 변환될 수 있는 포인터 또는 멤버 포인터 타입입니다:
-
- private, protected 또는 ambiguous 클래스에 대한 포인터 변환을 포함하지 않는 표준 포인터 변환 .
-
| (C++17부터) |
-
-
- 한정 변환 qualification conversion .
-
|
(C++11부터) |
catch ( ... ) 핸들러는 모든 유형의 예외와 일치합니다. 만약 존재한다면, 이는 핸들러 시퀀스에서 마지막 핸들러로만 사용될 수 있습니다. 이 핸들러는 nothrow exception guarantee 를 제공하는 함수에서 처리되지 않은 예외가 외부로 전파되는 것을 방지하기 위해 사용될 수 있습니다.
try { f(); } catch (const std::overflow_error& e) {} // 이 부분은 f()가 std::overflow_error를 던질 때 실행됨 (동일 타입 규칙) catch (const std::runtime_error& e) {} // 이 부분은 f()가 std::underflow_error를 던질 때 실행됨 (기본 클래스 규칙) catch (const std::exception& e) {} // 이 부분은 f()가 std::logic_error를 던질 때 실행됨 (기본 클래스 규칙) catch (...) {} // 이 부분은 f()가 std::string이나 int 또는 다른 관련 없는 타입을 던질 때 실행됨
핸들러들 중에서 일치하는 것이 발견되지 않으면, try 블록에 대한 일치하는 핸들러 검색은 동적으로 둘러싸는 try 블록에서 계속됩니다 (같은 스레드 내에서) (since C++11) .
일치하는 핸들러를 찾을 수 없는 경우, std::terminate 가 호출됩니다; 이 호출 전에 스택이 unwound 되는지 여부는 구현에 따라 정의됩니다.
예외 처리
예외가 발생하면, 제어는 일치하는 타입을 가진 가장 가까운 핸들러로 전달됩니다; "가장 가까운"이란 제어 스레드에 의해 가장 최근에 진입되었고 아직 종료되지 않은 try 키워드 뒤에 오는 복합 문(compound statement) 또는 멤버 초기화 리스트(있는 경우)에 대한 핸들러를 의미합니다.
핸들러 매개변수 초기화
매개변수 목록에 선언된 매개변수(있는 경우)는 "cv 한정자가 있을 수 있는
T
" 타입 또는 "cv 한정자가 있을 수 있는
T
에 대한 lvalue 참조" 타입으로,
예외 객체
(타입
E
)로부터 다음과 같이 초기화됩니다:
-
만약
T가E의 기본 클래스라면, 매개변수는 예외 객체의 해당 기본 클래스 서브객체를 지정하는T타입의 lvalue로부터 복사 초기화 됩니다. -
그렇지 않으면, 매개변수는 예외 객체를 지정하는
E타입의 lvalue로부터 복사 초기화됩니다.
매개변수의 수명은 핸들러 내에서 자동 storage duration 으로 초기화된 객체들의 소멸이 완료된 후, 핸들러가 종료될 때 끝납니다.
매개변수가 객체로 선언된 경우, 해당 객체에 대한 어떠한 변경도 예외 객체에 영향을 미치지 않습니다.
매개변수가 객체에 대한 참조로 선언된 경우, 참조된 객체에 대한 모든 변경은 예외 객체에 대한 변경이며 해당 객체가 다시 던져질 경우 효과를 발휘합니다.
핸들러 활성화
핸들러는 핸들러의 매개변수(있는 경우) 초기화가 완료되면 활성 상태로 간주됩니다.
또한, throw로 인해 std::terminate 가 호출될 때 암시적 핸들러가 활성 상태로 간주됩니다.
핸들러가 종료되면 해당 핸들러는 더 이상 활성 상태로 간주되지 않습니다.
가장 최근에 활성화되어 여전히 활성 상태인 핸들러와 연결된 예외를 현재 처리 중인 예외(currently handled exception) 라고 합니다. 이러한 예외는 재발생(rethrown) 될 수 있습니다.
제어 흐름
핸들러의 compound-statement 는 control-flow-limited statement 입니다:
void f() { goto label; // 오류 try { goto label; // 오류 } catch (...) { goto label: // 허용 label: ; } }
참고 사항
Stack unwinding 은 핸들러로 제어가 전달되는 동안 발생합니다. 핸들러가 활성화되면 스택 언와인딩은 이미 완료된 상태입니다.
throw 표현식에 의해 발생된 예외는 throw 0 포인터 또는 포인터-투-멤버 타입의 핸들러와 일치하지 않습니다.
|
(C++11부터) |
예외 객체 는 배열 또는 함수 타입을 가질 수 없으므로, 배열 또는 함수 타입에 대한 참조 핸들러는 어떤 예외 객체와도 일치하지 않습니다.
핸들러가 절대 실행될 수 없도록 작성하는 것이 가능합니다. 예를 들어, 명확한 public 기반 클래스에 대한 핸들러 뒤에 최종 파생 클래스에 대한 핸들러를 배치하는 경우가 있습니다:
try { f(); } catch (const std::exception& e) {} // f()가 std::runtime_error를 던질 경우 실행됨 catch (const std::runtime_error& e) {} // 데드 코드!
많은 구현체들이 CWG 이슈 388 의 해결 범위를 비-const 포인터 타입에 대한 참조자의 핸들러로 지나치게 확장합니다:
int i; try { try { throw static_cast<float*>(nullptr); } catch (void*& pv) { pv = &i; throw; } } catch (const float* pf) { assert(pf == nullptr); // 통과해야 하지만 MSVC와 Clang에서는 실패함 }
키워드
예제
다음 예제는 핸들러의 여러 사용 사례를 보여줍니다:
#include <iostream> #include <vector> int main() { try { std::cout << "Throwing an integer exception...\n"; throw 42; } catch (int i) { std::cout << " the integer exception was caught, with value: " << i << '\n'; } try { std::cout << "Creating a vector of size 5... \n"; std::vector<int> v(5); std::cout << "Accessing the 11th element of the vector...\n"; std::cout << v.at(10); // vector::at() throws std::out_of_range } catch (const std::exception& e) // caught by reference to base { std::cout << " a standard exception was caught, with message: '" << e.what() << "'\n"; } }
가능한 출력:
Throwing an integer exception... the integer exception was caught, with value: 42 Creating a vector of size 5... Accessing the 11th element of the vector... a standard exception was caught, with message: 'out_of_range'
결함 보고서
다음의 동작 변경 결함 보고서들은 이전에 발표된 C++ 표준에 소급 적용되었습니다.
| DR | 적용 대상 | 게시된 동작 | 올바른 동작 |
|---|---|---|---|
| CWG 98 | C++98 | switch 문이 핸들러로 제어를 전달할 수 있음 | 금지됨 |
| CWG 210 | C++98 | throw 표현식이 핸들러와 매칭됨 |
예외 객체가
핸들러와 매칭됨 |
| CWG 388 | C++98 |
포인터 또는 멤버 포인터 타입의 예외가
다른 타입의 const 참조로 매칭될 수 없음 |
변환 가능할 때
매칭 가능하도록 수정 |
| CWG 1166 | C++98 |
추상 클래스 타입에 대한 참조인
핸들러가 매칭될 때의 동작이 명시되지 않음 |
핸들러에 추상 클래스 타입이
허용되지 않음 |
| CWG 1769 | C++98 |
핸들러 타입이 예외 객체 타입의 기저 클래스일 때,
변환 생성자가 핸들러 매개변수 초기화에 사용될 수 있음 |
매개변수가 예외 객체의 해당 기저 클래스
서브오브젝트로부터 복사 초기화됨 |
| CWG 2093 | C++98 |
객체 포인터 타입의 예외 객체가 자격 변환을 통해
객체 포인터 타입의 핸들러와 매칭될 수 없음 |
허용됨 |
참조문헌
- C++23 표준 (ISO/IEC 14882:2024):
-
- 14.4 예외 처리 [except.handle]
- C++20 표준(ISO/IEC 14882:2020):
-
- 14.4 예외 처리 [except.handle]
- C++17 표준(ISO/IEC 14882:2017):
-
- 18.3 예외 처리 [except.handle]
- C++14 표준(ISO/IEC 14882:2014):
-
- 15.3 예외 처리 [except.handle]
- C++11 표준(ISO/IEC 14882:2011):
-
- 15.3 예외 처리 [except.handle]
- C++03 표준 (ISO/IEC 14882:2003):
-
- 15.3 예외 처리 [except.handle]
- C++98 표준(ISO/IEC 14882:1998):
-
- 15.3 예외 처리 [except.handle]