~cpp class ALA{ publid: virtual void processAdiption() = 0; ... }; class Puppy: public ALA{ publid: virtual void processAdiption(); ... }; class Kitten: pubic ALA{ publid: virtual void processAdiption(); ... };
~cpp ALA * readALA(istream& s);
~cpp void processAdoptions( istream& dataSource) { while (dataSource) { ALA *pa = readALA(dataSource); pa->processAdoption(); delete pa; } }pa에 해당하는 processAdoption()은 오류시에 exception을 던진다. 하지만, exception시에 해당 코드가 멈춘다면 "delete pa;"가 수행되지 않아서 결국 자원이 새는 효과가 있겠지 그렇다면 다음과 같이 일단 예외 처리를 한다. 물론 해당 함수에서 propagate해주어 함수 자체에서도 예외를 발생 시킨다.
~cpp void processAdoptions( istream& dataSource) { while (dataSource) { ALA *pa = readALA(dataSource); try{ pa->processAdoption(); } catch ( ... ) { delete pa; throw; } delete pa; } }
~cpp template<class T> class auto_ptr{ public: auto_ptr(T *p = 0) : ptr(p) {} private: T *ptr; };
~cpp void processAdoptions(istream& dataSource) { while (dataSource){ auto_ptr<ALA> pa(readALA(dataSource)); pa->processAdoption(); } }
~cpp void displayIntoInfo(const Information& info) { WINDOW_HANDLE w(createWindow()); display info in window corresponding to w; destroyWindow(w); }
~cpp class WidnowHandle{ public: WindowHandle(WINDOW_HANDLE handle) : w(handle) {} ~WindowHandle() {destroyWindow(w); } operator WINDOW_HANDLE() {return w;} private: WINDOW_HANDLE w; WindowHandle(const WindowHandle&); WindowHandle& operator=(const WindowHandle);
~cpp void displayIntoInfo(const Information& info) { WINDOW_HANDLE w(createWindow()); display info in window corresponding to w; }
~cpp class Image{ public: Image(const string& imageDataFileName); ... }; class AudioClip{ public: AudioClip(const string& audioDataFileName); ... } class PhoneNumber{ ... }; class BookEntry{ public: BookEntry(const string& name, const string& address = "", const string& imageFileName = "", const string& audioClipFileName = ""); ~BookEntry(); void addPhoneNumber(const PhoneNumber& number); private: string the Name; string the Address; list<phoneNumber> thePhones; Image *theImage; AudioClip *theAudioClip; };
~cpp // 기본 생성자 BookEntry::BookEntry(const string& name, const string& address, const string& imageFileName, const string& audioClipFileName) :theName(name), theAddress(address), theImage(0), theAudioClip(0) { if (imageFileName != ""){ // 이미지를 생성한다. theImage = new Image(imageFileName); } if (audioClipFileName != "") { // 소리 정보를 생성한다. theAudioCilp = new AudioClip( audioClipFileName); } } BookEntry::~BookEntry() { delete theImage; delete theAudioClip; }
~cpp if (audioClipFileName != "") { theAudioClip = new AudioClip(audioClipFileName); }
~cpp void testBookEntryClass() { BookEntry b( "Addison-Wesley Publishing Company", "One Jacob Way, Reading, MA 018678"); ... }
~cpp void testBookEntryClass() { BookEntry *pb = 0; try{ pb = new BookEntry( "Addison-Wesley Publishing Company", "One Jacob Way, Reading, MA 018678"); } catch ( ... ) { delete pb; throw; } delete pb; }
~cpp BookEntry::BookEntry(const string& name, const string& address, const string& imageFileName, const string& audioClipFileName) :theName(name), theAddress(address), theImage(0), theAudioClip(0) { try{ if (imageFileName != ""){ theImage = new Image(imageFileName); } if (audioClipFileName != "") { theAudioCilp = new AudioClip( audioClipFileName); } } catch (...){ delete theImage; delete theAudioClip; throw; } }
~cpp class BookEntry{ public: ... private: ... void cleanup(); // 이것이 객체를 삭제하는걸 묶은것 };
~cpp BookEntry::BookEntry(const string& name, const string& address, const string& imageFileName, const string& audioClipFileName) :theName(name), theAddress(address), theImage(0), theAudioClip(0) { try{ ... } catch (...){ cleanup(); throw; } } BookEntry::~BookEntry{ cleanup(); }
~cpp class BookEntry{ public : ... private: ... Image * const theImage; AudioClip * const theAudioClip; }
~cpp class BookEntry{ public : ... private: ... Image * initImage(const string& imageFileName); AudioClip * initAudioClip(const string& theAudioClip); } BookEntry::BookEntry(const string& name, const string& address, const string& imageFileName, const string& audioClipFileName) :theName(name), theAddress(address), theImage(initImage(imageFileName)), theAudioClip(initAudioClip(AudioCilpFileName)) {} // theImage는 가장 처음 초기화 되어서 자원이 세는 것에대한 걱정이 없다. 그래서 해당 소스에서 // 예외처리를 생략하였다. Image * initImage(const string& imageFileName) { if (imageFileName != "") return new Image(imageFileName); else return 0; } // theAudioClip는 두번째로 초기화 되기 때문에, 예외의 발생경우 이미 할당되어진 theImage의 자원을 // 해제해주는 예외 처리가 필요하다. AudioClip * initAudioClip(const string& theAudioClip) { try{ if (adioClipFileName != ""){ return new AudioClip(audioClipFileName); } else return 0; } catch ( ... ){ delete theImage; throw; } }
~cpp class BookEntry{ pubcli: ... private: ... const auto_ptr<Image> theImage; const auto_ptr<AudioClip> theAudioClip;그리고 생성자의 코드를 이렇게 한다.
~cpp BookEntry::BookEntry(const string& name, const string& address, const string& imageFileName, const string& audioClipFileName) :theName(name), theAddress(address), theImage(imageFileName != "" ? new Image(imageFileName) : 0), theAudioClip( audioClipFileName != "" ? new AudioClip(audioClipFileName):0) {}
~cpp BookEntry::~BookEntry() {}이렇게 아무것도 해주지 않아도 객체가 같이 파괴되고 리소스가 새는 것을 방지 할수 있다.
~cpp class Session{ public: Session(); ~Session(); ... private: static void logCreation(Session *objAddr); static void logDestruction(Session *objAddr); };
~cpp Session::~Session() { logDestruction(this); }
~cpp Session::~Sessioni() { try{ logDestruction(this); } catch ( ... ){ cerr << "해당 주소에서 세션 객체의 파괴기록이 되지 않습니다." << "-주소:" << this << ".\n"; }하지만 이것도 원래의 코드보다 안전할 것이 없다. 만약 operator<< 부를때 exception이 발생한다면 파괴자가 던지는 exception으로 다시 우리가 해결하고자 하는 원점으로 돌아가 버린다. 그렇다면
~cpp Session::~Sessioni() { try{ logDestruction(this); } catch ( ... ){ }이렇게 아무런 처리를 하지 않는다면 logDestuction에서 발생한 예외가 전달되는걸 막고 프로그램 중지를 위하여 스택이 풀려나가는걸 막을수는 있을 것이다.
~cpp Session::Session() { logCreation(this); startTransaction(); } Session::~Session() { logDestruction(this); endTransaction(); }이럴 경우에는 Session의 파괴자에게 문제를 제거하는 명령을 다시 내릴수 있따 하지만 endTransaction이 예외를 발생히킨다면 다시 try-catch문으로 돌아 갈수 밖에 없다.
~cpp class Widget { ... }; void f1(Widget w); void f2(Widget& w); void f3(const Widget w); void f4(Widget *pw); void f5(const Widget *pw); catch (Widget w) ... catch (Widget& w) ... catch (const Widget w) ... catch (Widget *pw) ... catch (const Widget *pw) ...
~cpp // 이 소스는 위의 Widget의 일환이라고 생각하면 무리 없겠다. istream operator>>(istream& s, Widget& w); void passAndThrowWidget() { Widget localWidget; cin >> localWidget; throw localWidget; }
~cpp void passAndThrowWidget() { static Widget localWidget; cin >> localWidget; throw localWidget; }
~cpp class Widget { ...} class SpecialWidget: public Widget { ... }; void passAndThrowWidget() { SpecialWidget localSpecialWidget; ... Widget& rw = localSpecialWidget; throw rw; // rw의 형 즉 Widget의 복사생성자가 작동하여 복사해 예외를 발생시킨다. }
~cpp catch (Widget& w) { ... throw; // 해당 객체를 다시 복사하지 않고 던지며, 해당 예외를 propagete 한다. } catch (Widget& w) { ... throw w; // 해당 객체를 다시 복사해서 그 사본을 propagate한다. }주석에 되어 있는데로, 생각해 보라. throw가 복사생성자를 호출하지 않아서 효율적이다. 그리고 throw는 어떠한 형이든 예외를 전달한는데 상관하지 않는다. 하지만, 사실 예외자체가 그 형에 맞게 던져지므로 걱정이 없다. 하지만 catch문에서 예외를 던지는 객체의 형태를 바꿀 필요성이 있을때 후자를 사용해야 겠다.
~cpp catch (Widget w) ... // 값으로 전달 catch (Widget& w) ... // 참조 전달 catch (const Widget& w) ... // 상수 참조로 전달전달된 객체는 간단히 참조로 잡을수 있다;그것은 상수 참조로 전달될 필요성은 없다. 그러나 상수 참조가 아닌 전달 임시 객체들은 함수를 부르는걸 허용하지 않는다.
~cpp catch (Widget w) ... // 값으로 전달
~cpp catch (Widget& w) ... // 참조 전달. catch (const Widget& w) ... // 상수-참조 전달우리는 지금까지 복사에 의한 지불을 생각할수 있는데 참조의 전달은 반대로 복사하는 작업이 없다. 즉, 한번의 복사 이후 계속 같은 객체를 사용하게 되는 셈이다.
~cpp double sqrt(double);를 이렇게 사용 할수 있다.
~cpp int i; double sqrtOfi = sqrt(i);Item 5에도 언급되어 있듯이 C++상에서의 암시적 변환은 광 범위하다. i형의 double변환은 가뿐? 하다. 하지만 다음을 보자
~cpp void f (int value) { try { if (someFunction() ) { // someFunction()이 참이면 int형의 변수를 예외로 던진다. throw value; } ... } catch (double d) { // 해당 핸들은 double일때만 예외를 잡을수 있다. 그럼 value를 던지는 ... // try에서의 예외는 절대로 잡히지 않는다. 의도한 바가 아니리라. } ... }이런 사항을 유의 해야 한다. 예외에서는 암시적 변환을 생각하지 않는다.
~cpp exception | +-logic_error | | | +-domain_error | +-invalid_argument | +-length_error | +-runtime_error | +-range_error +-underflow_error +-overflow_error
~cpp catch (runtime_error) ... // 이렇게 선언하면 runtime_error자식인 catch (runtime_error&) ... // range_error, underflow_error catch (const runtime_error&) ... // overflow_error이 다 잡히는 거다. catch (runtime_error*) ... // 이렇게 하면 runtime_error* catch (const runtime_error*) ... // range_error*, underflow_error*, overflow_error* 으로 잡히는 것이다.
~cpp catch (const void*) ...이렇게 하면 어떠한 포인터 type라도 잡을 것이다.
~cpp try{ ... } catch (logic_error& ex) { // 여기에서 모든 logic에러가 잡힌다. ... } catch (invalid_argument & ex){ // 이 문은 작동을 하지 않는다. 위의 catch구분에서 이미 잡아 버린다. ... }
~cpp try{ ... } catch (invalid_argument & ex){ // invalid_argument 예외는 이곳에서 잡힌다. ... } catch (logic_error& ex) { // 여기서 모든 다른 logic_error 관련은 이곳에서 잡힌다. ... }
~cpp class exception { ... }; void someFunction() { static exception ex; // 이렇게 메모리에 항상 존재하는 객체만을 전달할수 있다. ... throw &ex; // 포인터로 전달, 해당 함수 영역을 벗어나므로, static만이 살아 남을수 있다. ... } void doSomething() { try{ someFunction(); // 여기에서 exception *을 던진다. } catch ( exception *ex) { // exception* 을 잡고 아무런 객체도 복사되지 않는다. ... } }
~cpp void someFunction() { exception ex; // local 예외 객체인데 이 함수의 영역을 벗어나면 파괴되어 진다. ... throw &ex; // 그럼 이건 말짱 헛일이라는 소리 이미 파괴된 객체를 가리키고 있으니 ... }자 이건 나쁜 코드의 유형일 것이다. 주석에서 언급한것과 같이 함수에서 벗어나면 new나 static이 아닌이상 만들어진 객체는 파괴되어 진다. 그리고 catch에서는 파괴되어진 객체의 주소 값을 받게 되는 것이다.
~cpp void someFunction() { ... throw new exception; // 이것도 어폐가 있는게, new에서 예외 발생하면 어떻게 할것가? ... }
~cpp class exception { public: virtual const char * what() throw(); ... } class runtime_error:public exception{ ... }; class Validation_error : public runtime_error{ // 자 해당 객체는 runtime_error를 상속해서 만들었고 public: virtual const char * what() throw(); // 이 가상함수는 exception상에 있는 것이다. ... } void someFunction() { ... if ( a validation 테스트 실패){ throw Validation_error(); } ... } void doSomething() { try{ someFunction(); } catch (exception ex) { // item 12에서 언급했듯 exception의 자식 예외객체들은 다 잡힌다. cerr << ex.what(); // 값으로 부모만 복사했기 때문에 what() 가상함수는 exception상의 // 가상 함수가 불린다. 개발자의 의도는 Validation_error 상의 가상함수를 // 부르길 원하는 것 이었다. ... } }주석에 언급되어 있듯이 이 버전은 slicing 문제가 발생한다. 구차한 설명 귀찮다. 결론은 값으로(by value)의 예외 객체 전달은 이런 slicing 문제로 당신이 원하는 행동을 절대로 못한다.
~cpp void someFunction() { ... if ( validation 테스트 에러 ){ throw Validataion_error(); } ... } void doSomething() { try{ someFunction(); } catch (exception& ex){ // 이부분을 참조로만 바꾸었다. 이전의 예제와 특별히 바뀐게 없다. // 하지만 이부분이 바뀌어서 cerr << ex.what(); // 여기서의 가상 함수도 Validation_error의 메소드가 불린다. ... } }
~cpp extern void f1();자 그럼 예외 명세이 적용된 f2를 보자. 다음은 오직 int만을 예외로 던질것이다.
~cpp void f2() throw(int);f1이 f2의 함수 명세과 다른 예외를 던지더라도, C++상에서는 f2에서 f1를 부르는것을 허용한다.
~cpp void f2() throw(int) { ... f1(); ... }이런 유연한 경우는 만약 예외 명세에 관한 새로운 코드가 과거의 예외 명세가 부족한 코드와 잘 결합할수 있음을 보인다.
~cpp template<class T> bool operator==(const T& lhs, const T& rhs) throw() { return &lhs == &rhs; }이 템플릿은 oprator== 함수를 모든 형에 적용시키는 것이다. 아마 같은 주소에 같은 타입이면 true를 반환하지만 아니라면 그것은 false를 반환한다. 이런 템플릿은 아무런 예외도 던지지 않은 템플릿으로 부터 함수가 만들어지는 상태에 따라 적합한 예외가 포함된다. 하지만 그것은 꼭 사실이 아니다. 왜냐하면 operator&(주소 반환 operator)가가 꼭 같은 몇몇의 형들을 위해서 overload되었기 때문이다. 만약 사실이 그러하다면 operaotr&가 operator== 안쪽에서 불릴때 예외를 던질 것이다. 그렇게 되면 우리의 예외 명세는 거부되고, 곧장 unexpected 로 직진하게 되는거다.
~cpp typedef void (*CallBackPtr) (int eventXLocation, int event YLocation, void *dataToPassBack); class CallBack{ public: CallBack(CallBackPtr fPtr, void *dataToPassBack) :func(fPtr), data(dataToPassBack){} void makeCallBack(int eventXLocation, int eventYLocation) const throw(); private: CallBackPtr func; void *data } void CallBack::makeCallBack(int eventXLocation, int eventYLocation) { func(eventXLocation, eventYLocation); }이 코드에서 makeCallBack에서 func을 호출하는것은 func이 던지는 예외에 것을 알길이 없어서 예외 명세에 위반하는 위험한 상황으로 갈수 있다.
~cpp typedef void (*CallBackPtr) (int eventXLocation, int event YLocation, void *dataToPassBack) throw();다음과 같이 형이 선언되었으면 callback함수 등록시 아무것도 던지지 않는다는 조건이라면 예외를 발생할것이다.
~cpp // 예외 명세가 없는 함수 void callBackFcn1(int eventXLocation, int event YLocation, void *dataToPassBack); void *vallBackData; ... CallBack c1(callBackFcn1, callBackData); // 에러다 callBackFcn1은 여기에서 형이 맞지 않아. 에러를 던질것이다. // 예외 명세가 있는 함수 void callBackFcn2(int eventXLocation, int event YLocation, void *dataToPassBack); throw() CallBack c1(callBackFcn2, callBackData); // 보다시피 알맞는 형이다.
~cpp class UnexpectedException {}; void convertUnexpected() { throw UnexpectedException(); }
~cpp set_unexpected(convertUnexpected);이렇게 하면 unexpected예외는 convertUnexpected를 호출한다. 즉, 새로운 UnexpectedException 객체로 예외가 교체되었다. 하지만 제공되는 예외 명세에서 unexpected를 방지할려면 UnexpectedException 예외를 포함해야 한다. 예외를 객체로 던졌기에.. (만약 예외 명세에 UnexpectedException을 넣지 않았다면 unexpected가 교체되지 않은 것처럼 terminate가 불릴것이다.)
~cpp void convertUnexpected() { throw() // 이건 현제의 예외를 계속 던진다는 의미 } set_unexpected(convertUnexpected);만약 위와 같이 하고 bad_exception(표준 라이브러리 상의 exception의 기본 예외 클래스)를 당신의 모든 예외 명세에 포함시키면 당신은 결코 당신ㄴ의 프로그램이 불시에 멈추어 버리는것에 대한 걱정을 할 요는 없을 것이다. 거기다가 규정에 맞지않는 예외들도 bad_exception으로 교체되고 예외는 기본 예외 대신에 다시 던저 퍼진다.(propagate)
~cpp class Session{ public: ~Session(); ... private: static void logDestuction(Session *objAddr) throw(); }; Session::~Session() { try{ logDestruction(this); } catch ( ... ) {} }Session의 파괴자는 logDestruction을 호출한다. 하지만 명시작은 어떠한 예외도 해당 logDestruction에서 던지지 못하도록 막아놓았다. 한번 logDestuction이 실패할때 불리는 함수들에 대하여 생각해 보자. 이것은 아마 일어나지 않을 것이다. 우리가 생각한대로이건 상당히 예외 명세의 규정 위반으로 인도하는 코드이다. 이런 예측할수 없는 예외가 logDestruction으로 부터 퍼질때 unexpected가 풀릴 것이다. 기본적으로 그것은 프로그램을 멈춘다. 이 예제는 그것의 수정 버전이지만, 그런 수행을 Session 파괴자의 제작자가 원할까? 작성자는 모든 가능한 예외 를 잡으려고 노력한다. 그래서 그건 Session 파괴자의 catch블럭에서수행되는 것이 다다면 그건 불공평한 처사라고 보인다. 만약 logDestruction이 아무런 예외 명세를 하지 않는다면, I'm-willing-to-catch-it-if-you'll-just-give-me-a-chance 시나리오는 결코 일어나지 않을것이다. (이런 문제의 예방으로 unexpected의 교체에 대한 설명을 위해 언급해 두었다.)