boost::shared_ptr
여기서 불완전한 클래스란 클래스의 선언부가 명백하게 컴파일 유닛에 포함(#include) 되지 않고 단순히 forward reference (or forward declaration) 로 선언 되어진 클래스를 말합니다.
컴파일 시간을 줄이기 위해서는 클래스가 선언된 헤더를 포함하지 않고 가능한 forward reference 를 이용하여 선언하는 방법(Compilation firewall, etc.)은 GOTW를 비롯한 여러 문헌에서 이미 자세하게 설명되어 있습니다.
boost::shared_ptr을 선언 시에 불완전한 클래스 또는void형을 템플릿으로 받을 수 있다는 특징을 보다 쉽게 이해하기 위해서 이러한 특징을 가지지 못하는std::auto_ptr과 비교해 봅니다.
#include<memory> // std::auto_ptr
#include<boost/shared_ptr.hpp>// boost::shared_ptr
// Forward reference.
classincomplete_type;
classMyClass1
{
incomplete_type*p1_;
void*p2_;
};
classMyClass2
{
std::auto_ptr<incomplete_type>sp1_; // (1)
std::auto_ptr<void>sp2_; // (2)
};
classMyClass3
{
boost::shared_ptr<incomplete_type>sp1_; // (3)
boost::shared_ptr<void>sp2_; // (4)
};
위의 예제를 컴파일하면 (1) 에서 컴파일 경고가 (2) 에서는 컴파일 오류가 발생합니다.
(1) 번 컴파일 경고는std::auto_ptr 형 변수가 삭제될 때 저장되어 있는 불완전한 클래스형 객체의 소멸자가 호출되지 않는다는 내용입니다.
c:\...\memory(718) :warningC4150:deletionofpointertoincompletetype'incomplete_type';nodestructorcalled
x:\...\memory(717) :whilecompilingclasstemplatememberfunction'std::auto_ptr<_Ty>::~auto_ptr(void)'
이는std::auto_ptr형 변수가 선언 되어질 때에std::auto_ptr의 기본 생성자와 소멸자가 가시화 되는데 이 때std::auto_ptr의 소멸자에서 불완전한incomplete_type형 객체를 삭제하는delete문이 있기 때문입니다.
incomplete_type*pt= 0;
deletept;
이 문제는 위 코드를 컴파일 할 때 경고가 발생하는 것과 동일한 이치입니다. 특정 컴파일러에서는 이러한 경고 조차 발생하지 않는 경우도 있다고 합니다. 흔히std::auto_ptr을 pimpl 이디엄에 적용할 때 쉽게 이와 같은 문제를 접할 수 있습니다. 컴파일 오류를 발생시키는 대신 단지 경고만 주기 때문에 심각한 문제를 초래할 수도 있다는 것은 큰 문제점 이지만 컴파일 경고를 발생시키는 상황이 발생한다는 것 자체 부터std::auto_ptr의 단점이라고 할 수 있습니다.
(2) 번 오류의 경우는std::auto_ptr의 역참조 연산자 (operator *())가 템플릿 인자의 참조 형 즉void &를 리턴하려 하기 때문에 발생합니다. 이 컴파일 오류는 해결이 불가능합니다. 즉std::auto_ptr 은void형을 템플릿 인자로 받을 수 없습니다.
일반적으로 스마트 포인터는 raw 포인터의 특징을 최대한 유지한다는 디자인 패러다임 관점에서 보면std::auto_ptr의 위와 같은 제약들은 큰 결함이라고 할 수 있습니다. (반면에boost::shared_ptr은 불완전한 클래스나void형을 템플릿 인자로 받을 수 있습니다. 즉 raw 포인터의 특징을 그대로 계승하고 있다는 것을 알 수 있습니다.)
raw 포인터의 특징을 템플릿 클래스에서 어떻게 구현할 수 있을지 알아 봅니다.
template<classT>
classsimple_ptr
{
T*pt_;
public:
typedefTelement_type;
public:
simple_ptr(T*pt= 0) :pt_(pt) { }
~simple_ptr() {if(pt_)deletept_; }
public:
T*operator->()const{returnpt_; }
T&operator*()const{return*pt_; }
};
가장 기초적인 형태의 위와 같은 스마트 포인터 클래스는 위에서 설명한std::auto_ptr이 가지는 문제점을 똑같이 내포하고 있습니다.
우선void 형을 템플릿 인자로 받을 수 있도록 하는 문제를 살펴 봅니다.
classMyClass
{
simple_ptr<void>sp_;
};
위의 코드를 직접 컴파일 해보면void & simple_ptr<void>::operator *() const에서 컴파일 오류가 발생 한다는 사실을 알 수 있습니다. 이 문제는 언듯 쉽게 해결될 수 있을 것 처럼 보입니다. 다음과 같이 템플릿 특화를 이용한 trait 템플릿 클래스를 작성합니다.
template<classT>structsimple_ptr_traits
{
typedefT&reference;
};
template<>structsimple_ptr_traits<void>
{
typedefvoidreference;
};
그리고 위의 trait 템플릿 클래스를 이용해simple_ptr를 아래와 같이 수정합니다.
template<classT>
classsimple_ptr
{
T*pt_;
public:
typedefTelement_type;
typedeftypenamesimple_ptr_traits<T>::referencereference;
public:
simple_ptr(T*pt= 0) :pt_(pt) { }
~simple_ptr() {if(pt_)deletept_; }
public:
T*operator->()const{returnpt_; }
referenceoperator*()const{return*pt_; }
};
이제 아래의 코드를 컴파일 해보면 더 이상 오류가 발생하지 않음을 알 수 있습니다.
classMyClass
{
simple_ptr<void>sp_;
};
classFoo{ };
MyClass::MyClass() :sp_(newFoo) { }
더 이상 컴파일 오류는 발생하지 않지만 위의 코드는 여전히 문제점을 내재하고 있는 코드입니다.
simple_ptr<void>::pt_는void형 포인터입니다. 따라서simple_ptr<void>의 생성자에서는Foo형 대상 객체의 포인터가 입력으로 주어졌을 때 void 형 포인터로 암시적 형 변환이 이루어졌습니다. 임의의 포인터는 항상 void 형 포인터로 암시적 형 변환이 가능합니다.
그런데simple_ptr<void>의 소멸자에서는void형 포인터가 삭제됩니다. C++에서void 형 포인터를 삭제하는 것은 합법이지만 삭제되는 포인터가 사용자 정의 클래스의 객체를 가리키고 있는 경우에 이 사용자 정의 클래스의 소멸자가 호출되지 않습니다. 따라서 사용자 정의 클래스의 소멸자에서 특별한 해제 작업을 하는 경우라면 큰 결함을 초래할 수 있습니다.
직접적으로void 형 문제는 아니지만 이와 비슷한 유형의 문제점이 하나 더 있습니다.
classBase
{
public:
Base() { }
~Base() { }// non-virtual
};
classDerived:publicBase
{
Derived() { }
~Derived() { }// non-virtual
};
simple_ptr<Base>sp(newDerived);
Dervied클래스 형 포인터는Base클래스 형 포인터로 암시적인 변환이 가능하기 때문에 위의 코드는 컴파일 오류는 발생하지 않습니다. 문제점은 simple_ptr<Base>::pt_는Base클래스 형 포인터이고Base클래스의 소멸자가virtual로 선언되지 않았기 때문에simple_ptr<Base>형 변수sp가 삭제될 때Derived 클래스의 소멸자가 호출되지 않습니다.std::auto_ptr도 똑 같은 문제점을 가지고 있습니다.
이제 모든 문제는 "옳바른 소멸자가 호출되지 않는다" 라는 한가지 문제로 귀착되었습니다.
simple_ptr의 템플릿 인자로 주어지는 클래스 형에 관계 없이 실제simple_ptr로 할당 되어지는 객체 포인터의 클래스 형을 기억하고 있다가 소멸자에서 이 이 클래스 형으로 형 변환 후 객체를 삭제할 수 있기만 한다면 모든 문제가 해결 될 수 있습니다.
소멸자에서 옳바른 형 변환 후 삭제할 수 있게 하기 위해서 typeless deleter 를 작성 합니다. 여기서 편의 상 typeless deleter 는 타입 중성적인 (type neutral) 형태로 저장되지만 내부적으로 타입 안전(type safe) 하게 객체 포인터를 삭제하는 deleter 라고 정의 합니다.
template<classT>
classsimple_ptr
{
T*pt_;
typedefvoid(*typeless_deleter)(void*);
typeless_deleterdeleter_;
template<classU>
structtyped_
{
staticvoiddeleter(void*typeless_obj)
{
U*typed_obj=static_cast<U*>(typeless_obj);
deletetyped_obj;
}
};
public:
typedefTelement_type;
typedeftypenamesimple_ptr_traits<T>::referencereference;
public:
simple_ptr()
:pt_(0),deleter_(0)
{
}
template<classU>
simple_ptr(U*pu)
:pt_(pu),deleter_(&typed_<U>::deleter)
{
}
~simple_ptr()
{
if(pt_&&deleter_)
(*deleter_)(pt_);
}
public:
T*operator->()const{returnpt_; }
referenceoperator*()const{return*pt_; }
};
위와 같은 구현은 여러가지 typeless deleter 구형 방법 중에서도 가장 간단한 형태라고 할 수 있습니다.simple_ptr의 내장 템플릿 클래스에 정의되어지는static멤버 함수를 통해서void형의 포인터를 옳바른 형 변환 후에 삭제 할 수 있습니다.static함수이기 때문에 특정 클래스에 한정되지 않고 타입 중성적인(type neutral) 형태로 deleter를 저장하는 것이 가능합니다.
simple_ptr의 소멸자는 더 이상 명시적으로delete를 사용하여 객체 포인터를 삭제하지 않습니다. 따라서 (1) 번과 같은 컴파일 경고가 발생하지 않을 뿐만 아니라 typeless deleter 를 통해서 객체 포인터가 옳바르게 삭제 된다는 것을 보장합니다.
simple_ptr의 두 번째 생성자에 주어지는 객체 포인터의 클래스 형을 위한 typeless deleter를 생성하기 때문에 상속 관계의 클래스에서 소멸자가virtual로 선언 되지 않은 경우라도 옳바른 소멸자가 호출되어진다는 것을 보장합니다.
simple_ptr<void>sp1(newFoo);
simple_ptr<Base>sp2(newDerived);
위의 코드에서 sp1의 typeless deleter는 simple_ptr<void>::typed_<Foo>::deleter이며sp2의 typeless deleter 는simple_ptr<Base>::typed_<Derived>::deleter가 됩니다. 따라서sp1이 스코프를 벗어나며 삭제 될 때에는 저장된void형 포인터가Foo형으로 형 변환된 후에 삭제되고sp2가 삭제될 때에는 저장된Base형 포인터가Derived형으로 형 변환된 후에 삭제됩니다.
structsimple_ptr<void>::typed_<Foo>
{
staticvoiddeleter(void*typless_obj)
{
Foo*typed_obj=static_cast<Foo*>(typless_obj);
deletetyped_obj;
}
};
structsimple_ptr<Base>::typed_<Derived>
{
staticvoiddeleter(void*typless_obj)
{
Derived*typed_obj=static_cast<Derived*>(typless_obj);
deletetyped_obj;
}
};
boost::shared_ptr의 typeless deleter는 사용자가 직접 제공할 수 있는 functor 를 사용한 형태의 typeless deleter 입니다. 즉 위 에제의 typeless deleter 에서delete typed_obj;위치에 사용자 정의 functor가 대신할 수 있다고 생각할 수 있습니다. 사용자가 직접 deleter를 제공할 수 있다는 사실이boost::shared_ptr을 더욱 강력하게 만들어 주지만 불완전한 클래스나void형을 템플릿 인자로 사용할 수 있는 기본 구현 원리는 위에서 구현한simple_ptr과 매우 유사하다고 볼 수 있습니다.
마지막으로 실용적이지는 못하지만 연습(!) 삼아서 boost::scoped_ptr이 불완전한 클래스나 void 형을 템플릿 인자로 받을 수 있도록 수정 해봅니다.
template<classT>structscoped_ptr_traits
{
typedefT&reference;
};
template<>structscoped_ptr_traits<void>
{
typedefvoidreference;
};
template<classT>classscoped_ptr// noncopyable
{
private:
T*ptr;
scoped_ptr(scoped_ptrconst&);
scoped_ptr&operator=(scoped_ptrconst&);
typedefscoped_ptr<T>this_type;
typedeftypenamescoped_ptr_traits<T>::referencereference;
typedefvoid(*typeless_deleter)(void*);
typeless_deleterdeleter_;
template<classU>
structtyped_
{
staticvoiddeleter(void*typeless_obj)
{
U*typed_obj=static_cast<U*>(typeless_obj);
boost::checked_delete(typed_obj);
}
};
public:
typedefTelement_type;
scoped_ptr() :ptr(0),deleter_(0) // never throws
{
}
template<classU>
explicitscoped_ptr(U*p):ptr(p),deleter_(&typed_<U>::deleter)// never throws
{
}
~scoped_ptr()// never throws
{
if(ptr&&deleter_)
{
(*deleter_)(ptr);
}
}
voidreset()// never throws
{
this_type().swap(*this);
}
template<classU>
voidreset(U*p)// never throws
{
this_type(p).swap(*this);
}
referenceoperator*()const// never throws
{
return*ptr;
}
T*operator->()const// never throws
{
returnptr;
}
T*get()const// never throws
{
returnptr;
}
typedefT*this_type::*unspecified_bool_type;
operatorunspecified_bool_type()const// never throws
{
returnptr== 0? 0: &this_type::ptr;
}
booloperator! ()const// never throws
{
returnptr== 0;
}
voidswap(scoped_ptr&b)// never throws
{
T*tmp=b.ptr;
b.ptr=ptr;
ptr=tmp;
typeless_deletertmp_deleter=b.deleter_;
b.deleter_=deleter_;
deleter_=tmp_deleter;
}
};
'Programming > MFC' 카테고리의 다른 글
Visual Studio .Net 단축키 모음 (0) | 2008.02.26 |
---|---|
BSTR CString conversions (0) | 2008.01.17 |
trl::shared_ptr 사용방법 (0) | 2008.01.10 |
MessgeBox 졸료하기 (0) | 2008.01.08 |
BROWSEINFO 의 초기경로 설정 (0) | 2007.11.09 |