Smart Pointers
Smart pointer,也就是所謂的“智能指針”,是指那些能夠自我管理生命周期的指針對(duì)象。C++ 11之前,標(biāo)準(zhǔn)庫(kù)只提供了一種智能指針,即std::auto_ptr。不過(guò)std::auto_ptr用處有限,大家更多地是使用boost智能指針。隨著時(shí)間推移,智能指針正在變得越來(lái)越流行,越來(lái)越重要,所以標(biāo)準(zhǔn)委員會(huì)在制定C++ 11標(biāo)準(zhǔn)時(shí),將boost智能指針也納入到標(biāo)準(zhǔn)中。
C++ 11標(biāo)準(zhǔn)庫(kù)提供了四種智能指針:
unique_ptrshared_ptrweak_ptrauto_ptr
auto_ptr因?yàn)楣δ苡邢?,用處也有限,所以就不講了。至于weak_ptr,它的實(shí)現(xiàn)方式和shared_ptr很像,甚至大部分代碼是通用的,所以也不專(zhuān)門(mén)講了。下面我們重點(diǎn)分析一下unique_ptr和shared_ptr。
unique_ptr
顧名思義,unique_ptr就是一個(gè)原生指針獨(dú)一無(wú)二的擁有者和管理者。當(dāng)一個(gè)unique_ptr離開(kāi)其作用域時(shí),其管理的原生指針會(huì)被自動(dòng)銷(xiāo)毀。
標(biāo)準(zhǔn)庫(kù)中定義了兩種類(lèi)型的unique_ptr:
-
unique_ptr<T>,管理通過(guò)new獲得的原生指針。 -
unique_ptr<T[]>,管理通過(guò)new[]獲得的指針數(shù)組。
這兩種智能指針的實(shí)現(xiàn)方式大同小異,我們主要分析unique_ptr<T>的源碼。理解了unique_ptr<T>的實(shí)現(xiàn)原理,unique_ptr<T[]>的實(shí)現(xiàn)原理自然也就明了了。
unique_ptr的源代碼
// file: <memory>
template<class _Tp, class _Dp = default_delete<_Tp> >
class unique_ptr {
public:
typedef _Tp element_type;
typedef _Dp deleter_type;
typedef typename __pointer_type<_Tp, deleter_type>::type pointer;
private:
__compressed_pair<pointer, deleter_type> __ptr_;
// ...
};
unique_ptr的聲明包含兩個(gè)模板參數(shù),第一個(gè)參數(shù)_Tp顯然就是原生指針的類(lèi)型。第二個(gè)模板參數(shù)_Dp是一個(gè)deleter,默認(rèn)值為default_delete<_Tp>。default_delete是一個(gè)針對(duì)delete operator的函數(shù)對(duì)象:
// file: memory
template<class T>
struct default_delete {
void operator()(T* ptr) const noexcept {
delete ptr;
}
};
注意這行代碼:
typedef typename __pointer_type<_Tp, deleter_type>::type pointer;
__pointer_type是一個(gè)type trait,用來(lái)“萃取”出正確的指針類(lèi)型。為了方便理解,大可以認(rèn)為它和下面的代碼是等價(jià)的:
typedef _Tp* pointer;
unique_ptr內(nèi)部用__compressed_pair保存數(shù)據(jù),__compressed_pair是一個(gè)“空基類(lèi)優(yōu)化”的pair,閱讀源代碼時(shí),完全可以將它當(dāng)做一個(gè)std::pair來(lái)對(duì)待。
這基本就是unique_ptr的全部聲明,下面我們來(lái)看如何構(gòu)造一個(gè)unique_ptr:
// file: memory
template<class _Tp, class _Dp = default_delete<_Tp> >
class unique_ptr {
// ...
public:
// 默認(rèn)構(gòu)造函數(shù),調(diào)用pointer的默認(rèn)構(gòu)造函數(shù)
inline constexpr unique_ptr() noexcept {
: __ptr_(pointer()) {
}
// 將一個(gè)nullptr轉(zhuǎn)換為一個(gè)unique_ptr
inline constexpr unique_ptr(nullptr_t) noexcept
: __ptr_(pointer()) {
}
// 拷貝構(gòu)造函數(shù),注意參數(shù)類(lèi)型為pointer,而不是const point&
inline explicit unique_ptr(pointer __p) noexcept
: __ptr_(std::move(__p)){
}
// 移動(dòng)構(gòu)造函數(shù)
inline unique_ptr<unique_ptr&& __u) noexcept
: __ptr_(__u.release(),
std::forward<deleter_type>(__u.get_deleter())) {
}
// 移動(dòng)賦值
inline unique_ptr& operator=(unique_ptr&& __u) noexcept {
reset(__u.release());
__ptr_.second() = std::forward<deleter_type>(__u.get_deleter());
return *this;
}
inline ~unique_ptr(){reset();}
// ...
};
unqiue_ptr還定義了兩個(gè)很重要的函數(shù):reset(pointer)和release()。reset(pointer)的功能是用一個(gè)新指針替換原來(lái)的指針,而release()則是是放棄原生指針的所有權(quán)。
// file: memory
template<class _Tp, class _Dp = default_delete<_Tp> >
class unique_ptr {
// ...
public:
// 放棄對(duì)原生指針的所有權(quán),并返回原生指針
inline pointer release() noexcept {
pointer __t = __ptr_.first();
__ptr_.first() = pointer();
return __t;
}
// 用__p替換原生指針,被替換的指針最終被銷(xiāo)毀
inline void reset(pointer __p = pointer()) noexcept {
pointer __tmp = __ptr_.first();
__ptr_.first() = __p;
if (__tmp)
__ptr_.second()(__tmp);
}
到目前為止,unique_ptr還不像個(gè)指針,因?yàn)檫€缺少兩個(gè)方法:operator*和operator->:
// file: memory
template<class _Tp, class _Dp = default_delete<_Tp> >
class unique_ptr {
// ...
public:
inline add_lvalue_reference<_Tp>::type operator*() const {
return *__ptr_.first();
}
inline pointer operator->() const noexcept {
return __ptr_.first();
}
這幾乎就是unique_ptr的全部源代碼了,總的來(lái)說(shuō)比較容易理解。下面我們來(lái)分析一個(gè)稍微復(fù)雜一些的智能指針:shared_ptr。
shared_ptr
還是先從聲明入手:
// file: memory
template<class _Tp>
class shared_ptr {
public:
typedef _Tp element_type;
private:
element_type *__ptr_;
__shared_weak_count* __cntrl_;
// ...
};
shared_ptr內(nèi)部維護(hù)了兩個(gè)指針:一個(gè)是被其管理原生指針__ptr_,還有一個(gè)類(lèi)型為__shared_weak_count的指針__cntrl_。那么這個(gè)__shared_weak_count又是什么呢?
file: memory
class __shared_count {
// not copy constructible and not assignable
__shared_count(const __shared_count&);
__shared_count& operator=(const __shared_count&);
protected:
long __shared_owners_; // how many owners do I have?
virtual ~__shared_count();
public:
explicit __shared_count(long __refs = 0) noexcept
: __shared_owners(__refs){}
void __add_shared() noexcept;
bool __release_shared() noexcept;
};
class __shared_weak_count : private __shared_count {
long __shared_weak_owners_;
public:
explicit __shared_weak_count(long __refs = 0) noexcept {
: __shared_count(__refs),
__shared_weak_owners(__refs) {}
protected:
virtual ~__shared_weak_count();
public:
void __add_shared() noexcept;
void __add_weak() noexcept;
void __release_shared() noexcept;
void __release_weak() noexcept;
long use_count() const noexcept { return __shared_count::use_count();}
private:
virtual void __on_zero_shared_weak() noexcept = 0;
};
__shared_weak_count是個(gè)虛基類(lèi),從它聲明的類(lèi)成員可以看出,這個(gè)類(lèi)的作用應(yīng)該是管理引用計(jì)數(shù)。實(shí)際上,shared_ptr和weak_ptr內(nèi)部都聲明了__shared_weak_count*類(lèi)型的成員變量,也就是說(shuō),__shared_weak_count同時(shí)管理shared owner和shared weak owner,我個(gè)人認(rèn)為這種做法值得商榷。
虛基類(lèi)的作用類(lèi)似于接口,是沒(méi)法直接使用的,所以還必須定義一個(gè)“實(shí)在”類(lèi):
// file: memory
template<class _Tp, class _Dp, class _Alloc>
class __shared_ptr_pointer : public __shared_weak_count {
__compressed_pair<__compressed_pair<_Tp, _Dp>, _Alloc> __data_;
public:
inline __shared_ptr_pointer(_Tp __P, _Dp __d, _Alloc __a)
: __data_(__compressed_pair<_Tp, _Dp>(__p, std::move(__d)), std::move(__a)) {}
// ...
};
后面我們會(huì)看到,__shared_ptr_pointer正是shared_ptr的大內(nèi)總管,不僅要記錄shared_ptr的shared owner,還要負(fù)責(zé)分配內(nèi)存和銷(xiāo)毀指針等工作。所以__shared_ptr_pointer類(lèi)實(shí)際上有三個(gè)成員:
long __shared_owners_;
long __shared_weak_owners_;
__compressed_pair<__compressed_pair<_Tp, _Dp>, _Alloc> __data_;
理解了__shatrf_ptr_pointer,shared_ptr的源代碼就容易讀了:
// file: memory
template<class _Tp>>
class shared_ptr {
public:
typedef _Tp element_type;
private:
element_type* __ptr_;
__shared_weak_count* __cntrl_;
struct __nat{int __for_bool_;}; // placeholder
// ...
};
// 如果 Yp* 能夠轉(zhuǎn)換成 _Tp*,則可以由 _Yp* 構(gòu)造一個(gè)shared_ptr<_Tp>
template<class _Tp>
template<class _Yp>
shared_ptr<_Tp>::shared_ptr(_Yp* __p,
typename enable_if<is_convertible<_Yp*, element_type*>::value, __nat>::type)
: __ptr_(__p) {
unique_ptr<_Yp> __hold(__p);
typedef __shared_ptr_pointer<_Yp*, default_delete<_Yp>, allocator<_Yp> >_CntrBlk;
__cntrl_ = new _CntrBlk(__p, default_delete<_Yp>(), allocator<_Yp>());
__hold.release();
}
// copy constructor, increment reference count
template<class _Tp>
inline shared_ptr<Tp>::shared_ptr(const shared_ptr& __r) noexcept
: __ptr(__r.__ptr_), __cntrl_(__r.__cntrl_) {
if (__cntrl_)
__cntrl_->__add_shared();
}
// move constructor, does't increment reference count
template<class _Tp>
inline shared_ptr<T>::shared_ptr(shared_ptr&& __r) noexcept
: __ptr_(__r.__ptr_), __cntrl_(__r.__cntrl) {
__r.__ptr_ = 0;
__r.__cntrl_ = 0;
}
template<class _Tp>
shared_ptr<_Tp>::~shared_ptr(){
if (__cntrl_)
__cntrl_->__release_shared();
}
shared_ptr的實(shí)現(xiàn)雖然比unique_ptr復(fù)雜了一些,但是如果你能讀懂unique_ptr的源代碼,那shared_ptr的源代碼對(duì)你來(lái)說(shuō)也不算個(gè)事。因?yàn)槠年P(guān)系,對(duì)shared_ptr的分析就到這里。最后順便說(shuō)說(shuō)一個(gè)關(guān)于效率的話(huà)題,我們已經(jīng)看到了,shared_ptr內(nèi)部維護(hù)了兩個(gè)指針,如果你直接調(diào)用構(gòu)造函數(shù),就想這樣:
class Widget;
auto sp = shared_ptr<Widget>(new Widget());
這里實(shí)際分配了兩次內(nèi)存,第一次是調(diào)用new Widget()的時(shí)候,第二次則是在shared_ptr構(gòu)造函數(shù)的內(nèi)部構(gòu)造__cntrl_的時(shí)候。分配內(nèi)存是很昂貴的操作,所以標(biāo)準(zhǔn)庫(kù)提供了make_shared()函數(shù),讓你一次分配全部所需的內(nèi)存:
// file: memory
template<class _Tp, class ..._Args>
inline
typename enable_if<!is_array<_Tp>::value, shared_ptr<_Tp>>::type
make_shared(_Args&& ...__args) {
return shared_ptr<_Tp>::make_shared(std::forward<_Args>(__args)...);
}
template<class _Tp>
template<class ...Args>
shared_ptr<_Tp> shared_ptr<_Tp>::make_shared(_Args&& ...__args) {
typedef __shared_ptr_emplace<_Tp, allocator<_Tp>>_CntrlBlk;
typedef allocator<_CntrlBlk> _A2;
typedef __alocator_destructor<_A2> _D2;
_A2 __a2;
unique_ptr<_CntrlBlk, _D2> __hold2(__a2.allocate(1), _D2(__a2, 1));
::new(__hold2.get()) _CntrlBlk(__a2, std::forward<_Args>(_args)...);
shared_ptr<_Tp> __r;
__r.__ptr_ = __hold2.get()->get();
__r.__cntrl_ = __hold2.release();
return __r;
}
我們可以看到,確實(shí)只分配了一次內(nèi)存。注意內(nèi)存的分配是在這里:
unique_ptr<_CntrlBlk, _D2> __hold2(__a2.allocate(1), _D2(__a2, 1));
而不是:
::new(__hold2.get()) _CntrlBlk(__a2, std::forward<_Args>(_args)...);
這里是調(diào)用placement new, 在__hold2的地址上構(gòu)造一個(gè)__CntrBlk。__CntrBlk的類(lèi)型是__shared_ptr_emplace<T, Alloc>,它的定義如下:
// file: memory
template<class _Tp, class _Alloc>
class __shared_ptr_emplace : public __shared_weak_count {
__compressed_pair<_Alloc, _Tp> __data_;
public:
template<class ..._Args>
__shared_ptr_emplace(_Alloc __a, _Args&& ...__args)
: __data_(piecewise_construct, std::forward_as_typle(__a),
std::forward_as_tuple(std::forward<_Args>(__args)...)){}
// ...
};
可見(jiàn)make_shared()將shared_ptr的成員打包到一個(gè)__shared_ptr_emplace中,一次性在堆中構(gòu)造出一個(gè)__shared_ptr_emplace,然后再拆包分配給shared_ptr的成員變量。