[기본] C++ Exception에 대한 이해
저는 회사에서 업무를 진행하면서 C++ Exception을 사용해 본 경험이 별로 없었습니다. 따라서 보통은 에러가 발생되는 지점에 조건 을 걸어서 지정한 에러코드를 리턴하였었습니다. 그리고 쓴다고 하더라도 main함수 전체를 try문으로 묶어서 프로그램을 종료시키기 전, 에러 로그를 남기기 위한 굉장히 한정적인 용도 정도로만 사용했었습니다. 그러다 최근에 사이드 프로젝트를 진행 하면서, Exception을 제대로 사용하면 코드가 더 깔끔해지고 더 안정적인 프로그램을 만들 수 있을거 같다는 생각을 많이 하게 되었습니다. 따라서 이번 글은 C++ Exception에 대해 긍정적으로 생각했던 부분을 이야기 해보겠습니다.
우선 Exception을 사용하기 앞서 예전에 C++을 공부하면서 Exception을 사용하게 될 경우 runtime에 성능 저하가 있다는 이야기를 몇 번 들었었습니다. 그 당시에는 별다른 의심을 하지 않고 그 이야기를 그대로 믿었었는데, 최근에 관련하여 Stack Overfloat글을 보게 되었습니다. 이 글의 내용을 요약하면 Exception을 Handling하는 방법에는 2가지가 있습니다.
1. try block에서 exception catching을 동적으로 설정하여, 예외가 던져젔을때 Dynamic Chain의 핸들러를 검색하는 방식
2. 던저진 exception 를 처리하기 위해 정적 조회 테이블을 컴파일러가 생성하는 방식
1번째 방식은 try-block 구문의 속도에 영향을 미치지만, 2번째 방식은 try-block에 미치는 영향이 거의 zero cost에 가깝다고 합니다. 그렇다고 해서 단순히 상태를 나타내기 위해서 exception을 빈번하게 throwing하면 안됩니다.
그 이유는 아래의 표를 보면 쉽게 파악할 수 있듯이
그렇다면 Exception이 던져진 이후에 어떠한 동작을 수행하기에 이렇게 성능 하락이 발생할까요?
Exception이 던져진 이후에 동작은 아래의 순서로 진행됩니다.
- try block에서 직접 또는 간접적으로 호출하는 모든 루틴에서 exception이 발생하게되면, 예외 객체는 throw 피연산자에 의해 생성된 객체에서 생성됩니다.
- 이 시점에서 컴파일러는 발생한 유형의 예외를 처리할 수 있는 상위 실행 컨텍스트에서 catch 절을 찾거나 모든 유형의 예외를 처리할 수 있는 catch 처리기를 찾습니다.
- (catch 핸들러는 try 블록 다음에 나타나는 순서대로 검사하게 됩니다.)
- 일치하는 Handler를 여전히 찾을 수 없거나 해제 프로세스 중에 예외가 발생하게 되면, 사전 정의된 런타임 함수 종료terminate가 호출됩니다.
- 일치하는 Catch Handler가 발견되고 값으로 캐치되면 해당 형식 매개변수는 예외 객체를 복사하여 초기화됩니다. (parameter가 참조일 경우 참조로 초기화)
- formal parameter가 초기화되고 프로세스는 stack unwinding을 실행한다. try시작 부분 부터 throw 사이의 모든 automatic objects을 역순으로 파괴합니다.
그렇다면 본격적으로 Exception은 왜 사용해야 할까요?
그 이유는 Exception Throw을 피함으로써 얻은 효율성보다 Exception을 사용함으로써 얻는 높은 추상화가 코드의 복잡도를 낮쳐주고 이에 따라 코드의 퀄리티를 높여주지 때문입니다.
Microsoft문서에서는 Modern C++ 개발에서 C++ exception handling 사용을 권장하고 있습니다. 그리고 아래와 같은 근거를 제시하고 있습니다.
- 예외는 코드를 호출하여 오류 조건을 인식하고 처리하도록 강제합니다. 처리되지 않은 예외는 프로그램 실행을 중지합니다.
- 예외가 발생했다는 의미는 이 프로그램이 더이상 정상적인 동작을 할 수 없다는 걸 의미합니다. 따라서 이에 맞는 조치들이 강제되어야 합니다.
- 일반적으로 프로그램을 종료하지만, 프로그램의 성향에 따라서 상태를 초기화하고 재동작하도록 조치할 수 있습니다.
- 예외는 오류를 처리할 수 있는 호출 스택의 지점으로 이동합니다. 중간 함수는 예외가 전파되도록 할 수 있습니다. 다른 레이어와 조정할 필요가 없습니다.
- 에러 코드를 리턴하는 경우에 에러를 핸들링하는 코드가 더 상위 레이어에 있다면, 에러 코드를 전파해주는 지저분한 로직들이 필요합니다.
- 예외를 사용하면 오류를 감지하는 코드와 오류를 처리하는 코드를 완전히 분리할 수 있습니다.
- catch문에서 exception을 받아서 처리하게 코드를 분리할 수 있습니다.
- 예외 스택 해제 메커니즘은 잘 정의된 규칙에 따라 예외가 throw된 후 범위의 모든 개체를 삭제합니다.
- 특히 RAII idiom을 사용하면 exception이 발생했을 때, 메모리 해제를 위한 추가적인 코드가 필요 없습니다.
이 밖에 Microsoft문서에서는 아래 처럼 Exception 사용에 대한 Guideline도 제시해주고 있는데요.
이 내용은 한번쯤 확인해 보시면 좋을거 같습니다.
마지막으로 이 글에서 설명하지 하지 않은 Exception에 대한 기본적인 내용이나 Exception을 사용하기 위해 숙지되어야 하는 내용들은 아래의 블로그글을 참고해 보면 좋을거 같습니다.
씹어먹는 C ++ - <11. C++ 에서 예외 처리> (modoocode.com)
씹어먹는 C ++ - <11. C++ 에서 예외 처리>
modoocode.com