안녕하세요, katte입니다.
이번 글에서는 예외의 기본적인 메커니즘에 대해 알아보도록 하겠습니다.
예외 처리는 예외를 발생시키는(던지는) throw, 특별한 예외가 발생할 수 있는 throw 블록, 뒤이어 나오는 한 개 이상의 catch 블록으로 이루어져 있습니다. 또한 발생되는 예외는 클래스형이 일반적입니다.
예외가 발생되면 함수는 종료되며, try catch 블록이 있는 함수를 만날 때까지 호출된 함수들의 연쇄를 거슬러 올라가게 됩니다. 이를 스택 풀기라고 합니다.
프로그램은 함수를 호출할 때, 스택을 사용합니다.
다른 함수를 호출한(calling) 함수 구문의 주소를 스택에 올리고, 호출된(called) 함수를 실행합니다.
이때 호출된 함수의 매개변수 리스트와 local 변수 역시 스택에 추가됩니다.
호출된 함수가 종료되면 스택에 저장해둔 주소(함수를 호출했던 구문)로 넘어가고, 스택의 top이 해제됩니다.
이때, 스택에 저장되었던 local 변수가 해제되며 클래스 객체의 경우 소멸자가 호출됩니다.
이와 같은 매커니즘은 예외를 통해 함수가 종료될 때에도 적용됩니다.
단, 스택에서 만나는 첫번째 리턴 주소(해당 함수를 호출했던 구문의 주소)에서 멈추고 다음 코드를 실행하는 것이 아니라,
try블록에 들어있는 리턴 주소를 만날 때까지 연속적으로 스택이 해제됩니다.
또한 프로그램의 제어가 try 블록의 그 다음 구문이 아닌, catch 블록으로 넘어가게 됩니다.
물론 이 과정에서도 역시 local 변수가 해제되며 소멸자의 호출이 일어납니다.
이 일련의 과정을 스택 풀기라고 합니다.
클래스의 객체를 예외로 던질 때 일반적으로 throw 문과 객체의 생성을 결합합니다.
throw MyException(); //MyException 클래스 객체를 생성하고 던진다
또한 예외를 캐치할 때는 대개 참조를 사용하는데, 이는 자식 클래스의 객체 역시 잡을 수 있기 때문입니다.
그러나 여러 상속관계에 있는 클래스 예외들을 잡을 때는 catch 블록의 순서에 유의해야 합니다.
부모 클래스 parent와 자식 클래스 child가 있을 때,
catch(parent& pr)
{
}
catch(child& ch)
{
}
위와 같이 코드를 작성한다면 parent 객체와 child 객체 모두 첫번째 catch 블록으로 넘어가게 될 것입니다.
따라서 이런 상황을 원하지 않고 child 객체를 따로 처리하고 싶다면 상속 관계의 역순으로 catch 블록을 배치해야 합니다.
또한 예외 데이터 형에 생략 부호를 사용하면 모든 종류의 예외를 잡을 수 있습니다.
catch(...)
{
}
함수의 선언이나 정의 부분에서 예외를 지정할 수 있습니다. 이를 예외 지정이라고 합니다.
double harm(double a) throw(bad_thing); //bad_thing exception을 발생시킬 수 있다.
double marm(double a) throw(); //exception을 발생시키지 않는다.
double marm() noexcept; //예외를 발생시키지 않음
이러한 예외 지정을 사용하는 이유로는 사용자에게 try 블록의 필요성을 알려주고, 컴파일러로 하여금 런타임 체크용 코드를 추가하여 예외 사항이 위반되지는 않았는지 확인하게 하기 위해서입니다.
그러나 이러한 예외 지정은 좁은 범위 안에서 신중히 사용하여야 합니다.
지정되지 않은 예외가 발생된 경우 unexpected_exception(기대하지 않은 예외)이 발생하여 unexception 함수가 호출되고 프로그램이 종료됩니다. (이에 대해서는 다른 글에서 쓰도록 하겠습니다.)
'Computer > C++' 카테고리의 다른 글
[C++] 함수 포인터 헷갈리는 부분에 대하여 (0) | 2022.12.07 |
---|---|
[C++] 템플릿 클래스만 쓰면 링크 에러가 나는 이유가 뭘까 (0) | 2022.11.26 |
[C++] 스마트 포인터 클래스 (0) | 2022.11.20 |
[C++] value categories (0) | 2022.11.17 |
[C++] rvalue 참조와 move semantics (0) | 2022.11.16 |