C++ 이야기 여덟번째: boost::scoped_ptr 소개

Posted at 2007. 5. 3. 18:51 // in S/W개발/C++ 이야기 // by 김윤수


C++ 이야기 여덟번째입니다. 하나 하나 쌓여 가는 재미가 쏠쏠하네요. C++로 S/W를 개발하는 분들에게 유용한 내용일 거라고 저 혼자 감히 생각해 봅니다. ^^;

이번에는 boost의 scoped_ptr 에 대해 소개해 드릴까 합니다. scoped_ptr 은 이전에 소개해 드린, auto_ptr, shared_ptr 과 비슷하게 smart pointer 에 일종인데요, 그 활용 범위가 상당히 제한적인 smart pointer 입니다.

활용 범위가 상당히 제한적이라는 말에 벌써 관심을 꺼버린 분이 있으실까 노파심에 드리는 말인데, 그런 분들일수록 꼭 끝까지 읽어 보시기 바랍니다. 활용 범위가 상당히 제한적이기 때문에 역설적으로 상당히 유용한 smart pointer 이니까요.

그럼 어떤 경우에 유용하느냐 ? 오늘은 멀리 돌아가면서 얘기하지 않고 바로 핵심부터 말씀드리도록 하겠습니다. auto_ptr 과 비슷하게, 삭제될 때 가리키는 객체도 함께 삭제되기를 바라지만 auto_ptr의 이상한 복사 동작은 전혀 필요 없을 때입니다. 필요 없을 때라기 보다는 오히려 해가 될 때 또는 복사가 의미가 없을 때입니다.

이전에 이미 auto_ptr 에 대한 두 개의 글에서 비슷한 예제가 나왔었는데요

- C++ 첫번째 이야기: auto_ptr 템플릿 클래스 소개
- C++ 두번째 이야기: auto_ptr의 두 얼굴

여기에서 다시 한 번 설명 드리도록 하겠습니다.

첫번째는 class 멤버 변수 중 포인터를 통해 동적으로 생성된 객체를 가리키는데, 가리키는 객체가 삭제될 때, 가리킴을 당하는 객체도 함께 삭제되기를 원하지만, 포인터의 값이 복사되면서 발생하는 여러가지 골치아픈 문제는 신경쓰고 싶지 않을 때 쓸 수 있습니다.

class MyClassImpl;

class MyClass {
private:
  boost::scoped_ptr<MyClassImpl> pImpl;

public:
  ...
};

위와 같이 일반 포인터대신 scoped_ptr 을 사용하게 되면, MyClass 객체가 삭제될 때, pImpl 객체도 같이 삭제가 됩니다. 이 측면에서는 auto_ptr 과 비슷합니다. scoped_ptr 이 auto_ptr 과 다른 점은 scoped_ptr 은 boost::noncopyable 로 상속을 받기 때문에 복사할 수가 없습니다. scoped_ptr 멤버 변수를 복사할 수 없기 때문에 MyClass 객체도 복사할 수 없게 됩니다. 어떤 경우에는 이런 scoped_ptr의 특성이 불편하겠지만, 복사를 막아야 하는 경우가 있다면 상당히 유용할 것입니다. 복사를 막는 방법에 대한 상세한 내용은 C++ 이야기 다섯번째: 내 객체 복사하지마! 를 참고하시기 바랍니다.

다음은 scoped_ptr 의 인터페이스입니다.

namespace boost {
  template<class T> class scoped_ptr :
noncopyable {
    public:
      typedef T
element_type;
      explicit
scoped_ptr(T * p = 0); // never throws
     
~scoped_ptr(); // never throws
      void
reset(T * p = 0); // never throws
      T &
operator*() const; // never throws
      T *
operator->() const; // never throws
      T *
get() const; // never throws
      void
swap(scoped_ptr & b); // never throws
  };

  // never throws
  template<class T> void
swap(scoped_ptr<T> & a, scoped_ptr<T> & b);
}




위에서 보시다시피 noncopyable 을 상속 받는 것을 확인할 수 있습니다.

두 번째는 한 함수 내에서만 동적할당해서 쓰다가 함수를 끝낼 때는 삭제해야 하는 임시 객체를 처리할 때도 유용하게 쓰일 수 있습니다. 물론 이 임시 객체의 복사 동작이 전혀 의미가 없기 때문에 복사를 방지해야 하는 경우에 더 유용할 것입니다. 간단한 예제를 한 번 살펴 보겠습니다.

void f()
{
  boost::scoped_ptr<BigClass> bcsp(new BigClass());
  ......                              // 실행 중간에 예외가 발생하면 ?
} // 함수를 떠나기 전에 scoped_ptr 지역 변수에 대한
  // 소멸자가 호출되면서 new BigClass()로 할당한 객체가 해제됩니다.


함수를 떠나기 전에 delete 로 BigClass 객체를 일일이 삭제할 필요가 없습니다. 그리고, 함수 중간에 예외가 발생하더라도 stack unwinding 이 일어나면서 scoped_ptr의 소멸자가 호출되므로 scoped_ptr이 가리키던 BigClass 객체가 자동으로 삭제됩니다. try/catch 문으로 예외를 catch할 필요도 없이 예외 안전성이 확보된 셈입니다.

scoped_ptr 은 원래 auto_ptr의 ownership transfer 로 인한 문제점을 보완하기 위해 boost library내에 고안된 새로운 smart pointer 로서 이름에서 느낄 수 있듯이 특정 scope 내에서만 쓰일 객체에 대한 ownership 을 표현하는 용도로만 쓰이고 있습니다. 이전에 소개해 드린 shared_ptr의 shared ownership 또는 auto_ptr의 transfer of ownership 의미는 전혀 배제한체 말입니다. library의 인터페이스를 설계할 때 중요한 점 중에 하나가 사용하는 개발자들의 실수를 방지하고 사용 용도를 명확히 하는 것인데, 이런 측면에서 auto_ptr 은 약간 문제가 있었다고 할 수 있습니다. auto_ptr을 표준 컨테이너에 쓰면 큰 문제가 있다는 것이 그런 문제점 중에 하나였습니다. scoped_ptr 은 noncopyable로부터 상속을 받기 때문에 그러한 문제점이 발생할 여지를 아예 처음부터 예방하고, 그 사용 용도를 명확하게 한 것입니다.

마지막으로 정리하자면

scoped_ptr 은 복사할 수 없는 auto_ptr 이다

라는 정도로 기억해 두시면 좋을 것 같습니다.

다음 글로는 scoped_array, shared_array 에 대해 소개해 드리도록 하겠습니다.

혹시 scoped_ptr이 정말 유용하게 사용될 수 있는 예제나 제 글 중에 잘못된 부분, 보완할 부분이 있으시면 언제라도 댓글 환영입니다. 앞으로도 관심 있게 읽어 주시고, 소프트웨어 관련된 저의 다른 글들도 참고로 읽어 보세요.

소프트웨어는 soft 해야 제 맛이다
Flexible한 S/W 작성하기
소스코드 복사의 위험성
C++ 이야기 첫번째: auto_ptr 템플릿 클래스 소개
C++ 이야기 두번째: auto_ptr의 두 얼굴
C++ 이야기 세번째: new와 delete
C++ 이야기 네번째: boost::shared_ptr 소개
C++ 이야기 다섯번째: 내 객체 복사하지마!
C++ 이야기 여섯번째: 기본기 다지기(bool타입에 관하여)
C++ 이야기 일곱번째: auto_ptr을 표준 컨테이너에 담지 말라
Embedded System 의 Stack Size 제한