예외가 소멸자를 떠나지 못하도록 붙들어 놓자!


그냥 소멸자안에서 예외처리를 하면 안된다는 말인거 같다.

예제를 보며 문제점과 해결방안을 짚어보겠다.


class NPC{

public:

...

~NPC() { ... };

--> 예외를 던지는 소멸자라고 가정

};


void doSomething(){

vector<NPC> n;

...

}


vector 타입의 객체 n은 자신이 소멸될 때

자신이 가지고 있는 NPC객체를 소멸시킬 책임이 있다.

하지만 NPC를 소멸시키는 도중 예외가 발생되었다고 하자.

이 경우 프로그램은 미정의 동작을 보인다.

원인은 바로 예외를 내보내는 소멸자 때문!!

(NPC의 소멸자에서 예외를 내보내고 있다)




무슨 말인지 모르겠다. 다음 예를 보도록 하자.

class DBConnection {

public:

...

static DBConnection create();


void close();  

--> 연결을 닫는 함수. 연결이 실패하면 예외를 던진다고 가정.

--> 즉, 이번 항목 핵심인 예외를 던지는 함수

};


class DBConn{

private:

DBConnection db;

public:

...

~DBConn(){

db.close();

--> 소멸자에서 예외를 던지는 함수를 호출하고 있다.

}    

};


위의 예제는 데이터베이스의 연결을 관리하는

클래스 DBConnection과 그의 자원은 관리하는

클래스 DBConn이다.

여기서 주목할 점은 DBConnection의 멤버 함수

close()는 실패할 경우 예외를 던진다는 점이다.


{

DBConn dbc(DBConnection::create());

...

...

}


--> DBConnection 객체를 생성하고 DBConn으로

넘겨주어 관리를 맡기고 있다.

블록이 끝나면 DBConn 객체가 사라지고

사라지면서 자연스레 close()함수를 불러 연결을 닫게 된다.




하지만 close()는 실패할 경우 예외를 던진다!

때문에 close()를 호출하는 DBConn의 소멸자는

이 예외를 전파할 것이다. --> 문제 발생



해결책

1. 예외가 발생하면 프로그램을 끝내버린다.

~DBConn::~DBConn(){

try{ db.close() }

catch( ... ) {

...

abort();

}

}


-->  close()에서 예외를 던졌다.

                                                                                                          걍 프로그램을 끝내버린다.

                                                                                          미정의 동작으로 가기보다는 그냥 끝내버리겠다는 방법




2. 예외가 발생하면 삼켜버리겠다.

~DBConn::~DBConn(){

try{ db.close() }

catch( ... ) {

...

}

}


--> 그냥 무시하고 진행하겠다는 것.

      무엇이 잘못되었는지 알 수 없다.

      예외를 무시하더라도 그 다음 동작이

      제대로 이루어 진다는 보장이 있을 때 사용.




--> 두가지 모두 좋은 해결책은 아니다.

      사용자가 직접 해결할 수 잇도록 기회를 주도록 하자!


class DBConn{

private:

DBConnection db;

bool closed;

public:

...

void close(){

db.close();

closed = true;

}


~DBConn(){

if(!close)

try{

db.close();

}

catch( ... ){

 ...

}

}

};

--> 사용자가 연결을 직접 닫을 수 있게 close()함수를 제공

--> 즉, 사용자가 함수를 직접 불러 연결을 닫음

--> 따라서 발생하는 예외 또한 사용자가 처리해야함.




--> DBConn 객체가 소멸될 때, 사용자가 이미 close()함수를 불러

연결을 닫은 후라면, 소멸자에서 예외가 발생할 경우가 없어지게 된다.

    --> 연결이 열려있다면 소멸자 내의 db.close() 함수를 호출해서 닫아 준다.

--> 닫다가 실패하면 위의 두가지 방법 중 하나

      예외를 먹던지, 종료를 하던지 한다.




같은 말을 계속 반복하는거 같지만

쉽게 말해서 DBConn 클래스에 close()함수로

DBConnection의 연결을 직접 닫겠다는 것이다.

DBConn의 close()를 호출하기 되면 closed = true가 될 것이고

DBConn의 소멸자에서 예외를 던지지 않게되는 것이다.

--> 쉽게 말해 사용자가 직접 닫아라.



여기서 핵심!!

--> 예외는 소멸자가 아닌 다른 함수에서 비롯되어야 한다.

예외를 던지는 소멸자는 시한폭탄과도 같다.



POINT!!

1. 소멸자에서는 예외가 던져지면 안된다. 

   소멸자 안에서 호출된 함수가 예외를 던질 가능성이 있다면

   삼키던지 종료하던지 해라.


2. 어떤 클래스의 연산이 진행되다가 던진 예외에 대해 

   사용자가 직접 처리해야 할 경우, 해당 연산을 제공하는 함수는

   반드시 보통의 함수여야 한다. (소멸자 노노)





+ Recent posts