0.簡介
在C++編程世界中,內存管理是一把雙刃劍,手動管理帶來了極致的內存控制能力,但也帶來了像內存泄漏,野指針等問題;自動垃圾回收雖然安全,但卻會帶來一定的性能損耗。本文將介紹C++11引入shared_ptr,通過對其原理和源碼的解析,來了解其對于內存安全和性能的權衡。
1.原理
要了解一個設計,首先要看這個設計要解決的問題,shared_ptr的核心目標是實現安全的動態內存管理,所以內存安全就是其首要的任務;但也不能只考慮安全,也需要對像性能,易用性進行一些考慮,下面就在這幾個方面對shared_ptr的設計原理進行分析。
1.1 安全性設計
1)動態管理:分配控制的RAII實現,通過重載各種操作符來實現引用計數的增減和內存釋放。
2)線程安全:通過原子變量以及內存順序來保證線程安全。
3)釋放安全:可以自定義釋放使用的Deleter來進行釋放,可以利用這個特性做退出作用域的釋放,下面源碼分析會介紹。
1.2 性能設計
要看性能的設計首先要明確可能導致性能問題的點,第一個就是并發場景下引用計數的共享;然后就是shared_ptr本身控制塊的內存分配。
1)對于并發場景下的引用計數,可以看源碼解析中的三種枚舉,通過默認的原子操作,盡可能的降低影響。
2)對于內存分配,通過提供make_shared來實現一次性的分配,不多次申請內存。
1.3 易用性設計
易用性可以從以下方面進行考慮:
1)支持的類型:通過模板來支持各種類型。
2)創建方式:通過提供make_shared來支持更高效的創建方式。
3)使用方式:通過提供與裸指針一致的使用方式來降低使用要求。
4)和外部的集成:提供和標準庫良好的集成。
2.源碼解析
源碼分析我們先看其數據成員,然后看其主要的函數以及基于其函數我們可以得到的用法。
2.1 數據成員
shared_ptr繼承自__shared_ptr,其主要的成員也在__shared_ptr,其成員如下:
element_type* _M_ptr; // Contained pointer.
__shared_count<_Lp> _M_refcount; // Reference counter.
可以看到其中一個是傳入的指針,另外一個是引用計數。傳入的指針含義和實現比較明確,我們來看引用計數部分,首先引用計數包含兩個部分,一個是__shared_count的模板類,一個是_LP,我們一個個來看,先來看__shared_count,其是引用計數的核心類,其內成員主要是:
_Sp_counted_base<_Lp>* _M_pi;
通過_M_pi的下面兩個函數實現引用計數的加減,其內部封裝原子的操作:
void_M_weak_add_ref() noexcept{ __gnu_cxx::__atomic_add_dispatch(&_M_weak_count, 1); }void_M_weak_release() noexcept{// Be race-detector-friendly. For more info see bits/c++config._GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_M_weak_count);if (__gnu_cxx::__exchange_and_add_dispatch(&_M_weak_count, -1) == 1){_GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_M_weak_count);if (_Mutex_base<_Lp>::_S_need_barriers){// See _M_release(),// destroy() must observe results of dispose()__atomic_thread_fence (__ATOMIC_ACQ_REL);}_M_destroy();}}
接下來來看_LP,其類型如下:
// Available locking policies:// _S_single single-threaded code that doesn't need to be locked.// _S_mutex multi-threaded code that requires additional support// from gthr.h or abstraction layers in concurrence.h.// _S_atomic multi-threaded code using atomic operations.enum _Lock_policy { _S_single, _S_mutex, _S_atomic };
默認策略如下:
static const _Lock_policy __default_lock_policy =
#ifndef __GTHREADS_S_single;
#elif defined _GLIBCXX_HAVE_ATOMIC_LOCK_POLICY_S_atomic;
#else_S_mutex;
#endif
其決定是否需要內存屏障,通過特化模板實現:
template<_Lock_policy _Lp>class _Mutex_base{protected:// The atomic policy uses fully-fenced builtins, single doesn't care.enum { _S_need_barriers = 0 };};template<>class _Mutex_base<_S_mutex>: public __gnu_cxx::__mutex{protected:// This policy is used when atomic builtins are not available.// The replacement atomic operations might not have the necessary// memory barriers.enum { _S_need_barriers = 1 };};
2.2 關鍵函數和利用方式
關鍵的函數我們來看一些操作符重載以及自定義刪除和內存分配器的函數。
1)一些賦值的重載,讓其可控。
__shared_ptr&operator=(__shared_ptr&& __r) noexcept{__shared_ptr(std::move(__r)).swap(*this);return *this;}template<class _Yp>_Assignable<_Yp>operator=(__shared_ptr<_Yp, _Lp>&& __r) noexcept{__shared_ptr(std::move(__r)).swap(*this);return *this;}template<typename _Yp, typename _Del>_UniqAssignable<_Yp, _Del>operator=(unique_ptr<_Yp, _Del>&& __r){__shared_ptr(std::move(__r)).swap(*this);return *this;}voidreset() noexcept{ __shared_ptr().swap(*this); }
2)自定義內存分配器和釋放操作,其中自定義內存分配器直接按照接口定義就可以,較為常用的是自定義釋放,可以用來做退出作用域的操作。
template<typename _Yp, typename _Deleter,typename = _Constructible<_Yp*, _Deleter>>shared_ptr(_Yp* __p, _Deleter __d): __shared_ptr<_Tp>(__p, std::move(__d)) { }/*** @brief Construct a %shared_ptr that owns a null pointer* and the deleter @a __d.* @param __p A null pointer constant.* @param __d A deleter.* @post use_count() == 1 && get() == __p* @throw std::bad_alloc, in which case @a __d(__p) is called.** Requirements: _Deleter's copy constructor and destructor must* not throw** The last owner will call __d(__p)*/template<typename _Deleter>shared_ptr(nullptr_t __p, _Deleter __d): __shared_ptr<_Tp>(__p, std::move(__d)) { }/*** @brief Construct a %shared_ptr that owns the pointer @a __p* and the deleter @a __d.* @param __p A pointer.* @param __d A deleter.* @param __a An allocator.* @post use_count() == 1 && get() == __p* @throw std::bad_alloc, in which case @a __d(__p) is called.** Requirements: _Deleter's copy constructor and destructor must* not throw _Alloc's copy constructor and destructor must not* throw.** __shared_ptr will release __p by calling __d(__p)*/template<typename _Yp, typename _Deleter, typename _Alloc,typename = _Constructible<_Yp*, _Deleter, _Alloc>>shared_ptr(_Yp* __p, _Deleter __d, _Alloc __a): __shared_ptr<_Tp>(__p, std::move(__d), std::move(__a)) { }/*** @brief Construct a %shared_ptr that owns a null pointer* and the deleter @a __d.* @param __p A null pointer constant.* @param __d A deleter.* @param __a An allocator.* @post use_count() == 1 && get() == __p* @throw std::bad_alloc, in which case @a __d(__p) is called.** Requirements: _Deleter's copy constructor and destructor must* not throw _Alloc's copy constructor and destructor must not* throw.** The last owner will call __d(__p)*/template<typename _Deleter, typename _Alloc>shared_ptr(nullptr_t __p, _Deleter __d, _Alloc __a): __shared_ptr<_Tp>(__p, std::move(__d), std::move(__a)) { }
自定義釋放操作可以按照如下方式使用:
void test()
{int *pData = new(std::nothrow) int(10);std::shared_ptr scope_exit(nullptr,[&](void*){if(nullptr != pData)delete pData;});
}
2.3 問題和處理
shared_ptr會存在循環引用問題,這個可以使用weak_ptr解決,后面會專門對weak_ptr的實現原理和源碼進行分析。
3.總結
對于shared_ptr的使用,我們要知道它帶來的便利和問題。在開發領域沒有銀彈,只有取舍,也就是優秀的架構不是選擇完美的工具,而是理解每種工具的代價。所以在一些高性能關鍵領域可以不去使用shared_ptr,而一些常規領域建議使用。