본문 바로가기
Working on

[Effective C++] 자원 관리에는 객체를 활용한다

by Warehaus 2021. 5. 23.

C++ 에서 생성 된 자원 / 인스턴스를 해당 로직 내에서 해제하게 되는경우, delete 가 정상적으로 호출되지 않는 경우가 발생할 수 있다.

void f()
{
    Apple *a = createApple();
    ....
    delete a;
}

위 코드는 createApple에 의해 생성 된 Apple 인스턴스를 delete 하기를 기대하고 만들어 졌으나, '...' 내에서 예외가 발생하는 경우, a 의 메모리 해제가 정상적으로 이뤄지지 않을 수 있음을 말한다.

이렇게 생성 된 자원이 항상 해제될 수 있도록 만드는 방법은, 이 자원을 객체에 넣고 그 자원해제를 객체의 소멸자가 담당하도록 만든뒤, 그 소멸자는 실행제어가 f를 떠날 때 호출되게 하는 것이다.

자원을 객체에 넣으면, C++가 자동으로 호출하는 객체의 소멸자에 의해 해당 자원은 저절로 해제된다.

자원관리 객체를 사용함에 있어 중요한 두가지 법칙은,

1. 자원을 획득 후 자원 관리 객체에 전달

2. 자원 관리객체는 자신의 소멸자를 이용하여 자원을 확실하게 해제한다.

이다.

책에서는 auto_ptr를 사용하는 예시를 보여주는데, auto_ptr 의 유별난 특징을 알려준다.

auto_ptr는 자신이 소멸될 때, 가리키고 있는 대상에 대해 자동으로 delete를 하는 특성을 가지고, 이를 복사 시 원본 객체를 null로 만들어 주기까지 한다.

아래 코드를 보면 이해가 될 것이다.

std::auto_ptr<Test> p1 (createTest()); // Test 객체를 생성해서 포인터에 넣는다.
std::auto_ptr<Test> p2 (p1); // p2는 생성 된 객체를 가리키며 p1은 null

p1 = p2; // p1 은 생성 객체를 가리키며, p2 는 null

STL 컨테이너는 원소가 정상적인 복사동작을 가져야 하므로, auto_ptr를 원소로 허용하지 않고 있으며, 이에 대안으로 사용 가능한 것이 참조 카운팅 방식 스마트 포인터(Reference-counting smart pointer: RCSP)이다.  RCSP는 특정한 어떤 자원을 가리키는 외부 객체의 개수를 유지하다가 그 개수가 0이 되면 자원을 삭제하는 스마트 포인터이다.

아래는 RCSP 의 예시이다.

void f()
{
    std::tr1::shared_ptr<Test> p1 ( createTest());
    .....
    std::tr1::shared_ptr<Test> p2 ( p1); // p1, p2 모두 동일한 객체를 가리킴.
    p1 = p2; // 대입 후에도 동일한 객체를 가리킨다.
}

실제 업무를 진행하면서 이와 비슷한 자원관리를 하는 경우가 있는지 생각해 봤는데,

멀티 스레드 환경에서의 locking 관리가 꽤나 유사한 동작이라고 생각이 되었다.

Scoped lock (unique_lock, lock_guard) 의 경우 해당 스코프를 벗어나는 경우 자동으로 lock을 해제하는데, 이런 자원해제와 일맥상통한 개념이 아닌가 싶다. 

이번 항목은 자원관리를 객체로 하는 방법에 대한 내용이었으며, 이 내용을 통해 스코프 또는 함수를 벗어남에 따라 생성한 자원이 자동으로 소멸자에서 해제되게 하여 자원을 관리할 수 있도록 해야한다는 결론을 얻을 수 있다.