리소스 할당과 해제의 균형과 예외

Posted by epicdev Archive : 2011. 10. 3. 13:45
예외를 지원하는 언어는 리소스 해제에 복잡한 문제가 있을 수 있다. 예외가 던져진 경우, 그 예외 이전에 할당 된 모든 것이 깨끗이 청소된다고 어떻게 보장 할 수 있겠는가?


C++에서 예외와 리소스 사용의 균형

C++은 try...catch 예외 메커니즘을 지원한다. 불행하게도, 이 말은 예외를 잡은 다음 다시 던지는 루틴에서는 언제나 그 루틴에서 나가는 경로가 최소한 두 개는 존재한다는 얘기다.

void doSomething(void) {
    Node* n = new Node;
    try {
        // Do something
    } catch(...) {
        delete n;
        throw;
    }
    delete n;
}

우리가 생성한 노드가 해제되는 장소가 두 군데라는 점을 눈여겨보라. 하나는 루틴이 정상적으로 나가는 경로에 있고, 다른 하나는 예외처리 장소에 있다. 이것은 명백한 Don't repeat yourself 원칙 위반이며, 언제 터질지 모르는 유지보수 문제이기도 하다.

하지만 우리는 C++의 작동방식을 이용 할 수 있다. local 객체들은 자기를 둘러싼 블록에서 나갈 때 자동으로 파괴된다. 이것 덕분에 몇 가지 방법이 생긴다. 만약 상황이 허락한다면, 'n'을 포인터에서 스택에 놓이는 실제 Node 객체로 바꾸면 된다.

void doSomething(void) {
    Node n;
    try {
        // Do something
    } catch(...) {
        throw;
    }
}

이렇게 되면 예외가 생기든 그렇지 않든 Node 객체의 자동 파괴를 C++에게 맡길 수 있다.

포인터에서 다른 것으로 바꾸는 일이 불가능 하다면, 리소스를 다른 클래스로 감싸면 동일한 효과를 볼 수 있다.

class NodeResource {
    Node* n;
public:
    NodeResource() { n = new Node; }
    ~NodeResource() { delete n; }
    Node* operator->() { return n; }
};

void doSomething(void) {
    NodeResource n;
    try {
        // Do something
    } catch(...) {
        throw;
    }
}

이제 wrapper 클래스 NodeResource가 자신의 객체들이 파괴 될 때 관련 노드들 역시 파괴되도록 하는 일을 확실히 해준다. 편의성을 위해, wrapper 클래스는 -> 연산자도 제공해서 사용자가 Node 객체에 들어있는 필드에 바로 접근 할 수 있게 해준다.
이 기법이 너무나도 유용하기 때문에, 표준 C++ 라이브러리에도 동적으로 할당 된 객체들의 자동 wrapper를 제공하는 템플릿 클래스 auto_ptr이 있다.
void doSomething(void) {
    auto_ptr p (new Node);
    try {
        // Do something
    } catch(...) {
        throw;
    }
}

자바에서 리소스 사용의 균형


public void doSomething() throws IOException {
    File tmpFile = new File(tmpFileName);
    FileWriter tmp = new FileWriter(tmpFile);
    try {
        // Do something
    } catch(...) {
        // Do something
    } finally {
        tmpFile.delete();
    }
}

이 루틴에서 사용하는 임시파일은 루틴에서 어떻게 나가든 지워야 한다. Finally 블록이 우리가 뜻하는 바를 이렇게 간결하게 표현 해 준다.