- #include <iostream>
- #include <vector>
- using namespace std;
- class DBConnection
- {
- public:
- //DBConnection 객체를 반환하는 함수.
- static DBConnection create()
- {
- cout << "DBConnection::create()" << endl;
- DBConnection temp;
- return temp;
- }
- //연결을 닫는다. 연결이 실패하면 예외를 던진다.
- void close(){ cout << "DBConnection::close()" << endl; }
- };
- class DBConn
- {
- public:
- DBConn(DBConnection temp)
- {
- cout << "DBConn() 생성자" << endl;
- db = temp;
- }
- // 데이터베이스 연결이 항상 닫히도록 확실히 챙겨주는 함수
- ~DBConn()
- {
- cout << "~DBConn() 소멸자" << endl;
- db.close(); // close()함수에서 에러가 발생하면 문제가 발생한다.
- }
- private:
- DBConnection db;
- };
- int main()
- {
- // DBConnection 객체를 생성하고 이것을 DBConn 객체로 넘겨서 관리를 맡긴다.
- DBConn dbc(DBConnection::create());
- return 0;
- }
문제해결 하나! |
- DBConn::~DBConn()
- {
- cout << "~DBConn() 소멸자" << endl;
- try
- {
- db.close();
- }
- catch(...)
- {
- // close 호출이 실패했다는 로그를 작성.
- abort();
- }
- }
문제해결 둘! |
- DBConn::~DBConn()
- {
- cout << "~DBConn() 소멸자" << endl;
- try
- {
- db.close();
- }
- catch(...)
- {
- // close 호출이 실패했다믄 로그를 작성.
- }
- }
하지만 위 둘의 방법은 각자의 문제점을 가지고 있습니다. 중요한것은 close가 최초로 예외를 던지게 된 요인에 대해 프로글매이 어떤 조치를 취할 수 있는가인데, 이런 부분에 대한 대책이 없기 때문입니다. 그럼 더 나은 방법은 뭐가 있는지 살펴 보도록 하죠.
새로운 문제 해결 |
: 그렇다면 DBConn 인터페이스를 잘 설계해서, 발생할 소지가 있는 문제에 대처할 기회를 사용자가 가질 수 있도록 하면 어떨까요? 예를 들어 DBConn에서 Close 함수를 직접 제공하게 하면 이 함수의 실행 중에 발생하는 예외를 사용자가 직접 처리할 수 있을 것입니다. DBConnection이 닫혔는지의 여부를 유지했다가, 닫히지 않았으면 DBConn의 소멸자에서 닫을 수도 있을 것이고, 이렇게 하면 데이터베이스 연결이 누출 되지 않습니다. 하지만 소멸자에서 호출하는 close마저 실패한다면 이야기가 달라지겠지만 말이죠.
- #include <iostream>
- #include <vector>
- using namespace std;
- class DBConnection
- {
- public:
- // DBConnection 객체를 반환하는 함수.
- static DBConnection create()
- {
- cout << "DBConnection::create()" << endl;
- DBConnection temp;
- return temp;
- }
- // 연결을 닫는다. 연결이 실패하면 예외를 던진다.
- void close(){ cout << "DBConnection::close()" << endl; }
- };
- class DBConn
- {
- public:
- DBConn(DBConnection temp)
- {
- cout << "DBConn() 생성자" << endl;
- db = temp;
- }
- void close()
- {
- cout << "close() 함수 호출" << endl;
- db.close();
- closed = true;
- }
- ~DBConn()
- {
- cout << "~DBConn() 소멸자" << endl;
- if (!closed)
- {
- try
- {
- cout << "try::close() 함수 호출" << endl;
- db.close(); // 사용자가 연결을 안 닫았으면 여기서 닫는다.
- }
- catch(...)
- {
- // close 호출이 실패했다는 로그를 작성.
- }
- }
- }
- private:
- DBConnection db;
- bool closed;
- };
- int main()
- {
- DBConn dbc(DBConnection::create());
- dbc.close();
- return 0;
- }
위의 예제를 보면 사용자가 호출할 수 있는 close 함수를 둬서, 사용자에게 에러를 처리할 수 있는 기회를 주고 있습니다. 이것마저 없다면 사용자는 예외에 대처할 기회를 포착하지 못하게 될테죠. 사용자가 이 기회를 무시 했다고 해도 DBConn이 close 함수를 호출해 줄것이므로 문제는 없습니다.
* 소멸자에서는 예외가 빠져나가면 안됩니다. 만약 소멸자 안에서 호출된 함수가 예외를 던질 가능성이 있다면, 어떤 예외이든지 소멸자에서 모두 받아낸 후에 삼켜 버리든지 프로그램을 끝내든지 해야 합니다.
* 어떤 클래스의 연산이 진행되다가 던진 예외에 대해 사용자가 반응해야 할 필요가 있다면, 해당 연산을 제공하는 함수는 반드시 보통의 함수(즉, 소멸자가 아닌 함수)이어야 합니다.
1. abort로 프로그램 종료
2. 해당 함수에서 예외처리
3. 상속 받음 객체의 일반 함수에서 종료 체크후 종료 안되었을 경우 소멸자에서 한번더 종료
1. abort로 프로그램 종료
2. 해당 함수에서 예외처리
3. 상속 받음 객체의 일반 함수에서 종료 체크후 종료 안되었을 경우 소멸자에서 한번더 종료
댓글 없음:
댓글 쓰기