C++에서 기본 클래스(base class)와 파생 클래스(derived class)의 예외를 모두 처리하려면 반드시 파생 클래스의 catch 블록을 기본 클래스의 catch 블록보다 먼저 작성해야 합니다. 그렇지 않으면 파생 클래스의 catch 블록은 결코 실행되지 않습니다.
그 이유는 C++ 컴파일러가 catch 블록을 위에서부터 차례대로 검사하고, 파생 클래스 객체는 기본 클래스 타입으로 암시적 형 변환(업캐스팅)이 가능하기 때문입니다. 따라서 기본 클래스의 catch 블록이 먼저 위치하면 파생 클래스의 예외까지 모두 가로채 버리게 됩니다.
알고리즘
시작
클래스 B를 선언한다.
클래스 B를 상속받는 또 다른 클래스 D를 선언한다.
클래스 D의 객체를 생성한다.
try 블록에서 파생 클래스 객체를 throw 한다.
catch (D d)
"파생 클래스 예외가 잡혔습니다"를 출력한다.
catch (B b)
"기본 클래스 예외가 잡혔습니다"를 출력한다.
끝예제 1: 파생 클래스의 catch 블록이 먼저 오는 경우
다음은 파생 클래스의 catch 블록을 기본 클래스의 catch 블록보다 앞에 배치한 간단한 예제입니다. 실행 결과를 확인해 보겠습니다.
#include<iostream>
using namespace std;
class B {};
class D : public B {}; // D 클래스는 B 클래스를 상속
int main() {
D derived;
try {
throw derived;
}
catch(D derived) {
cout<<"Caught Derived Exception"; // 파생 클래스의 catch 블록
}
catch(B b) {
cout<<"Caught Base Exception"; // 기본 클래스의 catch 블록
}
return 0;
}출력
Caught Derived Exception
throw된 객체가 D 타입이므로, 먼저 검사되는 파생 클래스의 catch 블록이 정확히 매칭되어 해당 블록이 실행됩니다.
예제 2: 기본 클래스의 catch 블록이 먼저 오는 경우
이번에는 순서를 바꿔 기본 클래스의 catch 블록을 앞에 배치한 예제입니다. 결과가 어떻게 달라지는지 확인해 보겠습니다.
#include<iostream>
using namespace std;
class B {};
class D : public B {}; // D 클래스는 B 클래스를 상속
int main() {
D derived;
try {
throw derived;
}
catch(B b) {
cout<<"Caught Base Exception"; // 기본 클래스의 catch 블록
}
catch(D derived) {
cout<<"Caught Derived Exception"; // 파생 클래스의 catch 블록
}
return 0;
}출력
Caught Base Exception
결론
예제 2에서 throw된 객체는 분명 D 타입임에도 불구하고 "Caught Base Exception"이 출력되었습니다. 이는 파생 클래스 객체가 기본 클래스 타입으로 자동 변환될 수 있기 때문에, 먼저 등장한 기본 클래스의 catch 블록이 예외를 가로채 버렸기 때문입니다. 실제로 대부분의 컴파일러는 이런 경우 "D 타입의 예외는 앞선 핸들러에 의해 처리될 것"이라는 경고를 발생시킵니다.
따라서 기본 클래스와 파생 클래스를 모두 처리해야 하는 상황에서는 파생 클래스의 catch 블록을 항상 기본 클래스의 catch 블록보다 먼저 배치해야 하며, 그렇지 않으면 파생 클래스 전용의 예외 처리 로직은 영원히 실행되지 않는다는 사실이 실험을 통해 입증되었습니다.