shared_ptr에서의 deleter정보 저장

Tags:

Scott Myers가 최근에 쓴 글에서도 잠깐 언급이 되었고, comp.lang.c++.modreated에 제가 질문으로 올렸던 내용입니다. 질문내용은 이것입니다.

#include <iostream>
#include <tr1/memory>

using namespace std;
using namespace std::tr1;

class A
{
protected:
     virtual ~A() { }

};

class B: public A
{
public:
     virtual ~B() { }

};

int main()
{
     shared_ptr<A> a(new B());
     A *raw_a = a.get();
     delete raw_a;

     return EXIT_SUCCESS;

} 

여기서 delete raw_a는 실패합니다. 왜냐하면 A의 소멸자가 protected이기 때문입니다. 이 기법은 shared_ptr에 저장한 포인터를 외부에서 얻어갔다가 실수로 delete를 하는 걸 막는데 쓰일 수 있습니다.

만약 메인이 다음과 같다면,

int main()
{
     shared_ptr<A> a(new B());
     return EXIT_SUCCESS;

} 

이는 제대로 실행됩니다. 이것이 가능하려면 shared_ptr에서 new B()를 받았을 때 이를 그냥 A에 대한 포인터에 넣고 delete를 하면 안됩니다. 만약 그렇게 되면 다시 A의 소멸자가 protected이므로 에러입니다. 이를 막으려면 shared_ptr내에서는 new B()로 받은 B를 B형태의 포인터로 넣어야할 것이라고 생각했었는데, 그것을 어떻게 하는가가 질문입니다. 이원구님이 이에대해 답변을 주셨던데 오늘에야 봤습니다;;

struct impl_base
{
  impl_base(int c) : cnt(c) { }
  virtual ~impl_base() { }

  int cnt;

};

template <class U>
struct impl : impl_base
{
  impl(U* p) : impl_base(1), ptr(p) { }

  ~impl() {
    delete ptr;
  }

  U* ptr;

};

template <class T>
class smart_ptr
{
public:
  smart_ptr(T *p) : impl_(new impl<T>(p)), ptr_(p) { }

  template<typename Y> // (1)
  smart_ptr(Y *p) : impl_(new impl<Y>(p)), ptr_(p) { }

  ~smart_ptr() {
    --impl_->cnt;
    if (impl_->cnt == 0) delete impl_;
  }

  T* get() const {
    return ptr_;
  }  

private:
  impl_base* impl_;
  T* ptr_;

}; 

그러니까 shared_ptr<A>(new B())를 하면 생성자 (1)이 불립니다. 이는 impl_base* impl_ 에다 new impl<Y>를 저장합니다. 그렇기 때문에 B라는 타입 정보가 손실되지 않게되고, 따라서 B 타입으로서의 delete를 할 수 있습니다. 대단합니다.. 첫번째는 이 나이스한 코드때문에. 두번째는 해도 해도 모르는게 더 많은 이런 언어가 있다는 사실에. (더구나 많이 쓰이기까지;;)