소멸자로부터 예외가 발생한 경우를 한 번 생각해봐야 한다.
1
2
3
4
5
6
7
8
9
10
11
12 |
class Widget
{
public:
...
~Widget() { ... }
};
void DoSomething()
{
std::vector<Widget> v;
...
} |
cs |
위와 같이 vector v 는 자신이 소멸될 때, 자신이 거느리고 있는 Widget 또한 소멸 시켜줘야 한다.
그런데 만약 첫 번째 Widget을 소멸하는데 중에 예외가 발생한다면 어떻게 할 것인가?
또 다른 에로 데이터베이스 연결을 나타내는 클래스를 쓰고 있다고 가정해보자.
1
2
3
4
5
6
7
8 |
class DBConnection
{
public:
...
static DBConnection create(); //DBConnection 객체를 반환.
void close(); //DB 연결 해제.
}; |
cs |
사용자가 실수로 close를 호출하지 않을 것을 생각해서 별도로 자원 관리 클래스를 만드는 것이 좋은 방법이다.
(자원 관리는 뒤에서 자세히 다룬다고 함.)
1
2
3
4
5
6
7
8
9
10
11
12
13 |
class DBContorl //DBConnection 객체를 관리하는 클래스
{
public:
...
~DBControl() //데이터베이스 연결이 항상 닫히도록 챙겨주는 함수
{
m_db.close();
}
private:
DBConnection m_db;
}; |
cs |
그럼 하기와 같은 프로그래밍이 가능해진다.
1
2
3
4
5
6
7
8
9
10
11
12
13 |
int _tmain(int argc, _TCHAR* argv[])
{
DBControl dbc(DBConnection::create()); //DBConnection 객체를 생성하고
//이것을 DBControl 객체에 넘겨
//관리를 맡긴다.
...
return 0; //Block 끝에서 DBControl 객체가 소멸되면서
//자동적으로 DBConnection의 close 함수가
//호출된다.
} |
cs |
그런데 close 과정에서 예외가 발생한다면 DBControl의 소멸자는 이 예외를 전파할 것이고, 소멸자에서 처리가
정상적으로 진행되지 않게된다.
여기서 선택 할 수 있는 방법은 하기 2가지 방법이다.
1. close에서 예외가 발생하면 프로그램을 바로 끝내기.
1
2
3
4
5
6
7
8
9
10
11
12 |
~DBControl()
{
try
{
m_db.close();
}
catch (...)
{
//close 호출이 실패했다는 로그 작성.
std::abort();
}
} |
cs |
2. close를 호출한 곳에서 발생한 예외를 삼켜 버리기.
1
2
3
4
5
6
7
8
9
10
11 |
~DBControl()
{
try
{
m_db.close();
}
catch (...)
{
//close 호출이 실패했다는 로그 작성.
}
} |
cs |
대부분의 경우에서 예외 삼키기는 그리 좋은 방법은 아니다.
중요한 정보가 묻혀 버릴 수 있지만 24시간 내내 켜져있어야 하는 프로그램의 경우에는 예외로 인해 미정의 동작을
한다고 하더라도 그냥 예외를 먹어버리는게 나을 수도 있다.
단, '예외 삼키기'를 선택한 것이 제대로 빛을 보려면 발생한 예외를 무시한 뒤에도 프로그램이 신뢰성 있게 실행이 지속되야 한다.
어찌됬든 상기 2가지 방법 모두 특별히 좋을 건 없다.
(헷갈릴까봐 애기하는 것인데, 중요한 것은 close가 최초로 예외를 발생시키지 않는 방법이지만 그런 부분에 대해서 대책을 세울 수
없는 경우에 대해서 얘기하고 있는 상황이다.)
결국 이러한 상황에서 가장 안정적인 방법은
1. 소멸자가 아닌 일반 함수에서 close() 호출.
2. 혹시라도 일반 함수에서 close() 호출 시 문제가 생겼거나 사용자가 실수로 호출하지 않았을 것을 대비하여
3. 소멸자에서 close 여부 체크 후 close() 호출.
4. 만약 소멸자에서도 예외가 발생한다면 그땐 어쩔수 없이 실행을 끝내거나 예외를 무시하거나 해야함.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 |
class DBControl
{
public:
...
void close()
{
m_db.close();
m_bClosed = true;
}
~DBControl()
{
if (!m_bClosed)
{
try
{
m_db.close();
}
catch (...)
{
//close 호출이 실패했다는 로그 작성.
//실행을 끝내거나 예외를 삼키거나 선택.
}
}
}
private:
DBConnection m_db;
bool m_bClosed;
}; |
cs |
길게 얘기했지만 정리하자면... 내가 만든 프로그램이 아닌 Library, dll같은 곳에 있는 함수를 사용할 때에는
예외가 발생할 만한 함수를 소멸자에서 사용하지 않는 것이 좋다.
소멸자가 아닌 일반 함수에서 우선 처리를 한 후에 혹시라도 사용자가 실수로 놓칠 부분에 대해서 확인사살 용으로만
소멸자에 조건을 걸어 처리해주는 것이 가장 안정적인 방법이다.
소멸자에서 예외가 발생했더라도 try, catch 문으로 예외를 잡아줘야 한다.
2줄 요약
- 소멸자에서는 예외가 빠져나가면 안된다. 만약 소멸자 안에서 호출된 함수가 예외를 던질 가능성이 있다면,
어떤 예외이든지 소멸자에서 모두 받아낸 후에 삼켜 버리든지 프로그램을 끝내든지 해야 한다.
- 어떤 클래스의 연산이 진행되다가 던진 예외에 대해 사용자가 반응해야 할 필요가 있다면, 해당 연산을 제공하는
함수는 반드시 소멸자가 아닌 일반 함수이어야만 한다.
'Program Language > C++' 카테고리의 다른 글
항목10. 대입 연산자는 *this의 참조자를 반환하게 하자. (0) | 2017.07.26 |
---|---|
항목9. 객체 생성 및 소멸 과정 중에는 절대로 가상 함수를 호출하지 말자. (0) | 2017.07.25 |
항목7. 다형성을 가진 기본 클래스에서는 소멸자를 반드시 가상 소멸자로 선언하자. (0) | 2017.07.21 |
항목6. 컴파일러가 만들어낸 함수가 필요 없으면 확실히 이들의 사용을 금해 버리자. (0) | 2017.07.19 |
항목5. c++가 은근슬쩍 만들어 호출해 버리는 함수들에 촉각을 세우자. (0) | 2017.07.13 |