예외 처리와 템플릿은 C++의 가장 강력한 기능이다. 하지만 템플릿의 어떤 함수가 예외를 일으킬지 모르는 상태에서, 예외에 안전한 코드를 작성하기 어렵다.
이번 항목은 예외처리와 템플릿이라는 두 가지 주요 기능을 어떻게 하면 예외에 안전하고, 예외에 중립적인 일반 컨테이너를 작성하면서 배우게 해준다.
자 그럼 컨테이너의 기초격인 Statck 을 예외에 안전하고 중립적으로 만드는지 배워보도록 하자.
문제 : 어떻게 하면 생성자와 소멸자를 예외에 안전하도록 만들 수 있는가?
해결책
생성자
먼저 그럴듯한 이런 기본 생성자를 생각해 볼 수
있다. 이 기본 생성자가 예외에 안전하고 예외에 중립적인지 알아보기 위해 어떤 것이 예외를 발생할 수 있는지 생각해 봐야 한다. 첫번째 단계로
이 코드를 분석하여, 해제하는 함수와 생성자, 소멸자, 연산자, 그리고 다른 멤버 함수들을 포함하여 실제로 호출되는 함수들을 판단해
보자.
이 Stack 생성자는 먼저 vsize_ 를 10으로 할당하고 v에 new T[vsize_]를 이용하여 생성된 메모리를 할당한다. 할당할 때, 먼저 operator new[]()를 호출 하여 메모리를 할당한 다음, 총 vsize_만큼 T::T(기본 생성자)를 호출할 것이다.
여기서 두개의 예외에 대해서 생각할 수 있다.
첫째, operator new[]()가 메모리 할당에 실패하여 bad_alloc 예외를 발생할수 있다.
둘째, T::T 호출 시
개체를 파괴한다거나 할당된 메모리를 operator delete[]()를 통하여 자동적으로 해제한다는 보증 아래, 예외가 발생 될 수 있다. 즉
T::T 생성자 호출 시 예외가 발생 될 수 있다는 것이다.
그렇다면 첫째와 둘째의 예외는 어떻게 처리 해야만 할까? 사실 이 생성자에선 예외를 처리 할 것이 없다. 왜 그런지 알아보도록 하자.
첫째, 잡아서 처리할 내용이 아무것도 없다. 왜냐하면 operator new[]() 호출시 스스로 처리하거나 호출자에 상태(즉, 예외)를 전달한다. 현재에선 당연히 호출자가 생성자이므로 생성자에게 상태가 전달된다.
둘째, operator new[]() 호출시 bad_alloc(new에서 예외가 발생 되면 std::bad_alloc타입의 객체(bad_alloc) 예외가 발생 된다.) 예외로 종료 되면 v_에 할당 자체가 되지 않으며, T 생성자 호출 중 예외가 발생 되면, 완전히 생성된 T 개체는 파괴되고 operator delete[]()가 자동 호출되어 메모리도 해제 된다.
여기서 T 소멸자 호출시 예외가 한번 더 발생되면 더블 예외로 인하여 terminate() 호출로 프로그램이 강제 종료된다는 것은 배제한다고 한다.(Effective C++과 more Effective C++ 을 보면 자세히 나오니 참조 하도록)
셋째, vsize_에 10이 할당 되었는데, 이것은 사실 아무 의미가 없다. 왜냐하면 new 호출시 예외가 발생 되어 생성자 호출이 잘못됨을 나타내 주고, vsize_에 할당이 잘못되어 예외가 일어난다는 것 자체는 Stack 생성자 호출이 완전하지 않았다는 것을 의미하며, 이것은 온전히 생성자 호출이 되지 않았다는 것이다. 그러므로 자동적으로 Stack 객체는 생명을 갖기도 전에 사라지게 된다.
소멸자
아주 간단하게 이렇게 짤 수 있겠다. 왜냐하면 delete[] 는 절대 예외를 발생시키지 않기 때문이다. 단지 문제가 되는것은 T::~T() 호출시 예외 발생 될 수 있다. 그래서 소멸자에서 예외를 벹지 못하도록 해야지만 Stack 의 소멸자가 예외에 안전할 수 있다고 한다.(누군가 delete 를 오버로드 하여 예외를 발생시키게 한다면, 그건 .. 정말 위험한 행동이라고 한다.)
가이드 라인
기본적인 예외 안전 규칙을 기억하고, 소멸자나 오버로드 된 delete() 혹은
operator delete[]()에서 예외 빠저나가지 않도록 해야 한다. 모든 소멸자와 해제 함수에서 throw() 에외 용법을 가지고 있는
것처럼 작성 하자.
총평
Effective C++, More Effective C++ 에서 봤던 내용이 아른 아른 거리는 장이였다. 기본적으로 컨테이너가
예외에 안전하도록 짜기 위해선 생성자와 소멸자부터 예외에 안전하도록 짜야 하고, 소멸자와 delete 에선 예외를 벹지 않도록 해야 하고 new
에선 예외 처리에 대한 로직이 반드시 있어야 한다는 것, 그리고 이런 규칙을 따른 클래스는 막강한 예외 안전성을 손에 넣을 수 있다는 것을 알게
되었다.
'책 정리 > Exceptional C++' 카테고리의 다른 글
항목 38 : 개체의 정체(Object Identity) (난이도 : 5) (0) | 2008.10.08 |
---|---|
항목 12 : 예외에 안전한 코드를 작성하기 - 파트 5 (난이도 7) (0) | 2008.10.07 |
항목 11 : 예외에 안전한 코드를 작성하기 - 파트 4 (난이도 8) (0) | 2008.10.07 |
항목 10 : 예외에 안전한 코드를 작성하기 - 파트 3 (난이도 9½) (0) | 2008.10.07 |
항목 9 : 예외에 안전한 코드를 작성하기 - 파트 2 (난이도 8) (0) | 2008.10.07 |
예외에 안전한 코드에 대한 생각을 하게된 폭팔적인 계기 (0) | 2008.10.07 |
항목 18 : 코드 복잡성 - 파트 1 (난이도 9) (0) | 2008.10.06 |
항목 7 : 표준 라이브러리의 사용(혹은, 다시 보는 임시 개체) (난이도 5) (0) | 2008.10.06 |
항목 6 : 임시 개체들 (난이도 5) (0) | 2008.10.06 |
항목 5 : 최대 재사용 가능한 일반 컨테이너 - 파트 2 (난이도 6) (0) | 2008.10.06 |
최근댓글