티스토리 뷰

반응형

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

소멸자로 부터 예외가 발생 하는 경우를 C++ 언어에서 막는 것은 아니지만, 
실제 상황을 들춰보면 확실히 우리가 막을 수밖에 없다.

 

예제 1

1
2
3
4
5
6
7
8
9
10
11
12
13
class Widget
{
public:
    Widget();
    ~Widget() {….}
};
 
void main()
{
   std::vector<Widget> v;
}                          
 
 
cs

1. vector 타입의 객체가 v 일 경우 객체를 소멸시킬 책임은 바로 벡터에게 있다.
2. 10개의 객체 v가 vector에 있을 경우 첫 번째 객체가 소멸할 때 예외가 생기고 두 번째 객체가 또 예외가 생길 경우 C++입장에서는 감당하기 힘들다.
3. 위 2번의 일 경우 예외가 동시에 발생한 조건에 따라 프로그램 실행이 종료되든지 정의되지 않은 동작을 보이게 된다.
4. 이유는 예외가 발생 하는 것을 내버려 두는 소멸자에게 있다.

 

예제 2

예외를 던지고 실패 할 수 있는 코드를 소멸자에 넣어야 할 사람이 우리?

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
class A
{
public:
    static A create();
    void close();
}    
 
class b // A객체를 관리하는 클래스
{  
public:
    ~b();
    {
        a.close();
    }
 
private:
      A a;
};
 
 
void main()
{
   b temp(A::create());
}
 
cs

close() 호출만 성공하면 문제없는 코드이다. 
그러나 close()를 호출했는데 여기서 예외가 발생한다고 가정하면 b의 소멸자는 이 예외를 전파할것이다. 
다시 말해 소멸자에서 예외가 나가도록 내 벼러 둔다는 것이다. 
예외를 던지는 소멸자는 걱정거리를 의미한다.

 

걱정거리를 피하는 방법 1

 close에서 예외가 발생하면 프로그램을 바로  끝내버린다. 대게 abort를 호출한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
b::~b()
{
 
    try 
    { 
        a.close(); 
    }
 
    catch(…)
    {
 
        // Close 호출이 실패했다는 로그 작성;
        std::abort();
    }
 
}
cs

 

걱정거리를 피하는 방법 2

close를 호출한 곳에서 일어난 예외를 삼켜 버린다.

1
2
3
4
5
6
7
8
9
10
11
b::~b()
{
    try
    {  
        b.close();  
    }
    catch(…)
    {
       //Close 호출이 실패했다는 로그작성;             
    }
}
cs


예외를 삼키는 버리는 방법은 안좋은 방법이다. 무엇이 잘못됐는지의 정보가 묻혀버리기 때문이다.
하지만 때에 따라서는 불완전한 프로그램 종료 혹은 미정의 동작으로 인해 입는 위험을 감수하는 것보다
예외를 먹어버리는 게 나을수도 있다. 
단, 예외 삼키기를 선택한 것이 제대로 빛을 볼려면 발생한 예외를 그냥 무시한 뒤라도 프로그램이 신뢰성 있게 실행을 지속할 수 있어야 한다.

 

좋은 해법(발생할 소지가 있는 문제에 대처할 기회를 사용자가 가질 수 있도록)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class b
{
public:
    void close()
    {
        a.close();
        m_IsClosed = true;
    }
    ~b()
    {
        if(!m_IsClosed)
        {
            try {
                a.close();
            }
            catch (…) {
                //Close 호출이 실패했다는 로그 작성;
            }
        }
 
private :
       A a;
       bool m_IsClosed;
};
cs

Close 호출의 책임을 b의 소멸자에서 b의 사용자로 떠넘기는(확인사살코드를주고) 무책임한 책임 전가로
보일수 있지만 그런 것이 아니라 동작이 예외를 일으키면서 실패할 가능성 있고 또 그 예외를 처리해야 할 필요가 있다면 그 예외는 “소멸자가 아닌 다른 함수에서 비롯된 것이어야 한다”는 것이다.
위의 코드를 보면 사용자가 호출할수 있는 close()함수를 함수를 두긴했지만 부담을 떠넘기지는 않는다.
사용자가 에러를 처리할수 있는 기회를 주는 것이다.(이것마저 없다면 대처할 기회를 못잡게 된다)

 

결론

1. 소멸자에서는 예외가 빠져나가면 안된다. 만약 소멸자 안에서 호출된 함수가 예외를 던질 가능성이 있다면, 어떤 예외이든지 소멸자에서 모두 반아낸 후에 삼켜 버리든지 프로그램을 끝내든지 해야한다.
2. 어떤 클래스의 연산이 진행되다가 던진 예외에 대해 사용자가 반응해야 할 필요가 있다면, 해당 연산을 제공하는 함수는 반드시 보통의 함수(즉, 소멸자가 아닌 함수)이여야 한다.

반응형
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/04   »
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
글 보관함