boost::scoped_ptr의 동작 원리와 올바른 사용법

boost::scoped_ptr는 Boost C++ Libraries에서 제공하는 가장 단순한 형태의 스마트 포인터입니다. 이 포인터는 동적으로 할당된 객체의 소유권을 하나의 스코프에 묶어두고, 해당 스코프를 벗어나면 자동으로 delete를 호출해 메모리를 해제합니다. 따라서 예외가 발생하거나 함수가 중간에 반환되더라도 리소스 누출을 방지할 수 있습니다.

기본 사용법

다음 예제는 boost::scoped_ptr로 동적 객체의 생명 주기를 관리하는 모습을 보여줍니다.

#include <iostream>
#include <boost/scoped_ptr.hpp>

class Resource
{
public:
    Resource()  { std::cout << "Resource acquired\n"; }
    ~Resource() { std::cout << "Resource released\n"; }
};

int main()
{
    std::cout << "== start ==\n";
    {
        boost::scoped_ptr<Resource> holder(new Resource());
        std::cout << "inside scope\n";
    }
    std::cout << "== end ==\n";
    return 0;
}

실행하면 holder가 생성된 시점에 Resource가 만들어지고, 중괄호로 둘러싼 블록을 빠져나가는 순간 소멸자가 호출됩니다.

소유권 공유가 불가능한 이유

boost::scoped_ptr는 복사 생성자와 복사 대입 연산자를 private로 선언하여 복사를 금지하고 있습니다. 이로 인해 두 개의 scoped_ptr가 같은 객체를 동시에 관리할 수 없습니다.

int main()
{
    {
        boost::scoped_ptr<Resource> holder(new Resource());
        // boost::scoped_ptr<Resource> another(holder);   // compile error
        // boost::scoped_ptr<Resource> another = holder; // compile error
    }
    return 0;
}

이러한 특성 때문에 std::vector 같은 표준 컨테이의 요소로 boost::scoped_ptr를 사용할 수 없습니다. 컨테이너는 요소를 복사하거나 이해야 하는데, scoped_ptr는 복사가 지되어 있기 때문입니다.

#include <vector>

int main()
{
    boost::scoped_ptr<Resource> holder(new Resource());
    std::vector<boost::scoped_ptr<Resource> > store;
    // store.push_back(holder); // compile error
    return 0;
}

같은 원시 포인터를 여러 개로 관리하면 위험합니다

복사는 막혀 있지만, 동일한 원시 포인터를 각각 다른 scoped_ptr에 넘기는 실수는 여전히 가능합니다.

int main()
{
    Resource* raw = new Resource();
    {
        boost::scoped_ptr<Resource> holder1(raw);
        boost::scoped_ptr<Resource> holder2(raw);
    } // double delete -> undefined behavior
    return 0;
}

두 개의 scoped_ptr은 서로 다른 객체로 인식하고 스코프를 어날 때 각각 delete를 호출하므로, 두 번 삭제가 발생합니다.

swap과 reset

소유권을 이전할 수는 없지만, 두 scoped_ptr 간에 관리 대상을 교환할 수는 있습니다. 또한 reset 멤버 함수로 명시적으로 리소스를 해제할 수 있습니다.

int main()
{
    {
        boost::scoped_ptr<Resource> first(new Resource());
        boost::scoped_ptr<Resource> second(new Resource());

        first.swap(second); // first와 second가 관리하는 객체를 교환
        first.reset();      // first가 관리하던 객체를 즉시 삭제
        std::cout << "after reset\n";
    }
    return 0;
}

내부 구조 요약

다음은 boost::scoped_ptr의 핵심 구조를 크게 단순화한 것입니다.

namespace boost {
    template <typename T>
    class scoped_ptr : private boost::noncopyable
    {
    public:
        explicit scoped_ptr(T* p = 0) : ptr(p) {}
        ~scoped_ptr() { delete ptr; }

        T* get() const { return ptr; }
        T* operator->() const { return ptr; }
        T& operator*() const { return *ptr; }

        void reset(T* p = 0)
        {
            if (p != ptr) {
                delete ptr;
                ptr = p;
            }
        }

        void swap(scoped_ptr& other)
        {
            using std::swap;
            swap(ptr, other.ptr);
        }

    private:
        T* ptr;
        scoped_ptr(const scoped_ptr&);             // not defined
        scoped_ptr& operator=(const scoped_ptr&);   // not defined
    };
}

심은 RAII를 통해 객체의 생명 주기를 명시적 스코프에 묶고, 복사 금지를 통해 단일 소유권을 보장하는 것입니다.

언제 사용해야 할까

  • 예외가 발생할 수 있는 스코프 안에서 동적 객체를 사용할 때
  • 함수 내 여러 조건 분기로 인해 delete 호출이 누락되기 쉬울 때
  • 동적 객체의 생명 주기를 명확한 블록으로 제한하고 싶을 때
  • 예외 안전성이 중요한 코드

boost::scoped_ptr는 std::unique_ptr이 보급되기 전까지 C++에서 RAII 기반의 단일 소유권 스마트 포인터를 구현하는 대표적인 도구였습니다.

태그: Boost scoped_ptr RAII smart-pointer C++

10월 1일 16:21에 게시됨