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를 할 수 있습니다. 대단합니다.. 첫번째는 이 나이스한 코드때문에. 두번째는 해도 해도 모르는게 더 많은 이런 언어가 있다는 사실에. (더구나 많이 쓰이기까지;;)