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 기반의 단일 소유권 스마트 포인터를 구현하는 대표적인 도구였습니다.