C++ 에서 타 언어의 try-finally 흉내내기

by Lyn posted Jan 05, 2015
?

단축키

Prev이전 문서

Next다음 문서

ESC닫기

크게 작게 위로 아래로 댓글로 가기 인쇄

상당히 많은 언어가 예외 처리기를 가지고 있는데, 각각의 언어별로 특성이 있습니다.
try - catch - finally 를 모두 가지고 있는 언어(C#, Java 등) 도 있고

try - except (C 계열의 try - catch 에 대응) 와 try - finally 를 가지고 있지만 try - except 뒤에 finally 가 붙을 수 없어서 아래와 같이 중첩을 해야 하는 Pascal 계열의 언어(주로 Delphi 겠지만..) 도 있습니다


try

  try 

    //do something

  except

    //do something

finally

  //do something 


네 그리고 대망의 C++은.. finally 그런거 없습니다 (...)
C++ 표준협회에서는 stack 에 생성된 객체의 소멸자를 이용하라고 하는데 다른 언어의 코드를 C++로 옮기다가 보면 해제할것 하나하나마다 객체 만들어 주기도 보통 일이 아닙니다. 하지만 의외로 타 언어의 샘플을 C++로 옮기는게 자주 발생 하는 일이기도 하지요.


그래서 전 람다를 이용한 꼼수를 이용해서 코드를 옮길때 자주 써먹고 있습니다.
바로 아래와 같은 객체를 사용 해서요


class Finalizer
{
public:
    Finalizer()
    {

    }
    Finalizer(std::function<void(void)> fPtr)
    {
        this->fPtr = fPtr;
    }
    ~Finalizer()
    {
        if (fPtr != nullptr) fPtr();
    }
    void SetFunction(std::function<void(void)> fPtr)
    {
        this->fPtr = fPtr;
    }
private:    
    std::function<void(void)> fPtr;
};


소멸자에서 function 을 호출해 주느 매우 단순한 구조입니다.
이걸 어떻게 쓰냐면 


void test1()
{
    char* arr = new char[100];
    Finalizer final([&]
    {
        delete[] arr;
        wcout << L"Test1 : Delete Arr" << endl;
    });

    //do Something
    wcout << L"Test1 Code End" << endl;
    
    return;
}


이렇게 씁니다.

저 Finalizer 의 생성자로 lambda 를 넘겨서 finally 블록으로 삼는거죠.
그럼 포커스를 잃는순간 호출 되면서 실행 되게 됩니다.

final1.png


그럼 exception 이 발생 했을때는 어떨까요?
간단하게 아래와 같은 코드를 생각해 볼 수 있습니다


void test2()
{
    try
    {
        char* arr = new char[100];
        Finalizer final([&]
        {
            delete[] arr;
            wcout << L"Test2 : Delete Arr" << endl;
        });
        //do Something
        wcout << L"Test2 : Code End" << endl;
        
        throw L"Exception";
        
        return;        
    }
    catch (...)
    {
        wcout << L"Test2 : Exception" << endl;
    }    
}


하지만 이 코드는 문제가 하나 있는데, 코드 흐름이 다르다는겁니다

final2.png


블록을 빠져나오는것이 catch 로 넘어가는것보다 우선이기 때문에 finally 로 사용하려 했던 코드가 예외처리 코드보다 먼저 실행되어 버립니다.

이걸 해결하는건, C++은 블럭을 자유롭게 구성할 수 있다는걸 이용해서.. 바깥에 블록을 하나 더 만들어서 해결합니다




void test3()
{
    {
        Finalizer final;
        try
        {
            char* arr = new char[100];
            final.SetFunction([&]
            {
                delete[] arr;
                wcout << L"Test3 : Delete Arr" << endl;
            });
            //do Something
            wcout << L"Test3 : Code End" << endl;

            throw L"Exception";

            return;
        }
        catch (...)
        {
            wcout << L"Test3 : Exception" << endl;
        }
    }
}


위 코드는 어차피 저 코드밖에 없기 때문에 추가적인 블록이 필요 없긴 하지만... 일반적으론 그런 코드는 많지 않겠지요


final3.png


실행결과는 다음과 같습니다.
finally 블록이 다른 언어에서의 코드와 다르게 맨 뒤가 아닌 중간에 와야 하는게 맘에 들진 않는데...
대신 다른 언어에서 try 블록 내부에 선언된 변수를 finally 에서 쓸 수 없어 변수만 위쪽으로 옮기거나 하는 단점이 사라져 좋아진 것도 있습니다.


뭐 어차피 급하게 쓸때 쓰는 "꼼수" 라서요


PS. 테스트는 메모리 해제 코드로 했는데..... 메모리는 그냥 unique_ptr 이나 shared_ptr 계열 쓰세요 (...)

PS2. 더 좋은 방법 있으시면 좀 알려주세요.


아래는 전체 테스트 코드입니다

#include <iostream>
#include <functional>

using namespace std;

class Finalizer
{
public:
    Finalizer()
    {

    }
    Finalizer(std::function<void(void)> fPtr)
    {
        this->fPtr = fPtr;
    }
    ~Finalizer()
    {
        if (fPtr != nullptr) fPtr();
    }
    void SetFunction(std::function<void(void)> fPtr)
    {
        this->fPtr = fPtr;
    }
private:    
    std::function<void(void)> fPtr;
};


void test1()
{
    char* arr = new char[100];
    Finalizer final([&]
    {
        delete[] arr;
        wcout << L"Test1 : Delete Arr" << endl;
    });

    //do Something
    wcout << L"Test1 Code End" << endl;
    
    return;
}

void test2()
{
    try
    {
        char* arr = new char[100];
        Finalizer final([&]
        {
            delete[] arr;
            wcout << L"Test2 : Delete Arr" << endl;
        });
        //do Something
        wcout << L"Test2 : Code End" << endl;
        
        throw L"Exception";
        
        return;        
    }
    catch (...)
    {
        wcout << L"Test2 : Exception" << endl;
    }    
}

void test3()
{
    {
        Finalizer final;
        try
        {
            char* arr = new char[100];
            final.SetFunction([&]
            {
                delete[] arr;
                wcout << L"Test3 : Delete Arr" << endl;
            });
            //do Something
            wcout << L"Test3 : Code End" << endl;

            throw L"Exception";

            return;
        }
        catch (...)
        {
            wcout << L"Test3 : Exception" << endl;
        }
    }
}

void wmain()
{
    test1();
    wcout << endl;
    test2();
    wcout << endl;
    test3();
}
TAG •