C++智慧指標實現
上一篇介紹記憶體池的文章中提到一個基於記憶體池的智慧指標。C++沒有GC機制,需要程式設計師自己管理記憶體,而智慧指標則是C++程式設計師管理記憶體的利器。智慧指標的原理早已廣為人知,通俗來講就是用類來表示指標(用類來表示物件是OOP思想的核心),成員函式裡構建一個指向物件的指標,在建構函式,賦值函式,複製建構函式,解構函式等會改變物件個數的地方增加或減少引用計數,當引用計數為0時,釋放指標指向的記憶體,從而避免了程式設計師手動釋放,減少了記憶體洩漏和野指標的危險。C++11引入了智慧指標和弱指標,極大的方便了編碼。何為弱指標?弱指標的物件持有指向這塊記憶體的指標,卻不增加引用計數。為什麼要新增這個機制呢?因為智慧指標看起來完美無缺,卻有一個致命的缺陷:
class A;
class B {
public:
std::shared_ptr<A> _a_ptr;
~B() { std::cout << "BBB" << std::endl; }
};
class A {
public:
std::shared_ptr<B> _b_ptr;
~A() { std::cout << "AAA" << std::endl; }
};
int main() {
{
std::shared_ptr<A> a(new A );
std::shared_ptr<B> b(new B);
a->_b_ptr = b;
b->_a_ptr = a;
}
int a = 0;
a++;
}
當智慧指標迴圈引用的時候,記憶體沒有得到釋放!!這就是弱指標上場的機遇。
class A {
public:
std::weak_ptr<B> _b_ptr;
~A() { std::cout << "AAA" << std::endl; }
};
我們只要將其中一個類的指標型別改為弱指標,就能夠有效的釋放佔有的記憶體。不過弱指標使用時需要轉換為智慧指標使用。
我在這裡借鑑C++11智慧指標的實現,實現了基於記憶體池的智慧指標和弱指標,從而避免了再手動釋放記憶體給記憶體池的步驟。這裡我們要實現的功能是將從記憶體池中獲取到的記憶體交給一個類的物件來管理,這個物件保持一個關於這個記憶體的引用者的引用計數,當計數為零時將這塊記憶體還給記憶體池。
首先我們定義一個管理引用計數的類,把引用計數和指標管理的類分開來存放,這個概念在
//reference count class
class CRefCount {
public:
// construct
CRefCount() : _uses(1), _weaks(0) {}
// ensure that derived classes can be destroyed properly
virtual ~CRefCount() noexcept {}
// increment use count
void IncrefUse() {
_uses++;
}
// increment weak reference count
void IncrefWeak() {
_weaks++;
}
//decrement use count
bool DecrefUse() {
if (--_uses == 0) {
return true;
}
return false;
}
// decrement weak reference count
bool DecrefWeak() {
if (--_weaks == 0) {
return true;
}
return false;
}
// return use count
long GetUseCount() const {
return _uses;
}
// return true if _uses == 0
bool Expired() const {
return (_uses == 0);
}
private:
std::atomic_long _uses;
std::atomic_long _weaks;
};
下面我們實現一個指標的基類,實現了兩種指標所共有的操作:
// base class for CMemSharePtr and CMemWeakPtr
template<typename T>
class CBasePtr {
public:
typedef CBasePtr<T> _BasePtr;
typedef std::function<void(T*&)> _PtrDeleter;
typedef std::function<void(CRefCount*&)> _RefDeleter;
typedef std::pair<_PtrDeleter, _RefDeleter> _Deleter;
// construct
CBasePtr() noexcept : _ptr(0), _ref_count(0), _deleter(std::make_pair(nullptr, nullptr)){}
CBasePtr(T* ptr, CRefCount* ref) noexcept : _ptr(ptr), _ref_count(ref), _deleter(std::make_pair(nullptr, nullptr)) {}
CBasePtr(T* ptr, CRefCount* ref, _Deleter& func) noexcept : _ptr(ptr), _ref_count(ref), _deleter(func) {}
// construct CBasePtr object that takes resource from _Right
CBasePtr(const _BasePtr& r) : _ptr(r._ptr), _ref_count(r._ref_count), _deleter(r._deleter) {
if (_ref_count) {
_ref_count->IncrefUse();
}
}
// construct CBasePtr object that takes resource from _Right
CBasePtr(_BasePtr&& r) : _ptr(r._ptr), _ref_count(r._ref_count), _deleter(r._deleter) {
r._ptr = nullptr;
r._ref_count = nullptr;
r._deleter = std::make_pair(nullptr, nullptr);
}
// construct CBasePtr object that takes resource from _Right
_BasePtr& operator=(_BasePtr&& r) {
_ptr = r._ptr;
_ref_count = r._ref_count;
_deleter = r._deleter;
r._ptr = nullptr;
r._ref_count = nullptr;
r._deleter = std::make_pair(nullptr, nullptr);
return (*this);
}
// construct CBasePtr object that takes resource from _Right
_BasePtr& operator=(const _BasePtr& r) {
_ptr = r._ptr;
_ref_count = r._ref_count;
_deleter = r._deleter;
if (_ref_count) {
_ref_count->IncrefUse();
}
return (*this);
}
// return use count
long UseCount() const noexcept {
return (_ref_count ? _ref_count->GetUseCount() : 0);
}
// return pointer to resource
T* Get() const noexcept {
return (_ptr);
}
// test if expired
bool Expired() const noexcept {
return (!_ref_count || _ref_count->Expired());
}
// release resource
void Reset() {
Reset(0, 0);
}
// release resource and take ownership from CMemWeakPtr _Other._Ptr
void Reset(const _BasePtr& other) {
Reset(other._ptr, other._ref_count, other._deleter);
}
// release resource and take _Other_ptr through _Other_rep
void Reset(T *other_ptr, CRefCount * other_rep, _Deleter& deleter) {
if (other_rep)
other_rep->IncrefUse();
_Reset0(other_ptr, other_rep, deleter);
}
// release weak reference to resource
void Resetw() {
_Resetw(0, 0);
}
// release weak reference to resource and take _Other._Ptr
void Resetw(_BasePtr& other) {
Resetw(other._ptr, other._ref_count, other._deleter);
}
void Resetw(T *other_ptr, CRefCount *other_rep, _Deleter& func) {
if (other_rep)
other_rep->IncrefWeak();
_Resetw0(other_ptr, other_rep, func);
}
protected:
// release resource and take _Other_ptr through _Other_rep
void Reset(T *other_ptr, CRefCount * other_rep) {
if (other_rep)
other_rep->IncrefUse();
_Reset0(other_ptr, other_rep);
}
// release resource and take new resource
void _Reset0(T *other_ptr, CRefCount *other_rep) {
_DecrefUse();
_ref_count = other_rep;
_ptr = other_ptr;
_deleter = std::make_pair(nullptr, nullptr);
}
// release resource and take new resource
void _Reset0(T *other_ptr, CRefCount *other_rep, _Deleter& func) {
_DecrefUse();
_ref_count = other_rep;
_ptr = other_ptr;
_deleter = std::make_pair(nullptr, nullptr);
}
// decrement use reference count
void _DecrefUse() {
if (_ref_count && _ref_count->DecrefUse()) {
_Destroy();
}
}
// decrement use reference count
void _DecrefWeak() {
if (_ref_count && _ref_count->DecrefWeak()) {
_DestroyThis();
}
}
// point to _Other_ptr through _Other_rep
void _Resetw(T *other_ptr, CRefCount *other_rep) {
if (other_rep)
other_rep->IncrefWeak();
_Resetw0(other_ptr, other_rep);
}
// release resource and take new resource
void _Resetw0(T *other_ptr, CRefCount *other_rep) {
_DecrefWeak();
_ref_count = other_rep;
_ptr = other_ptr;
_deleter = std::make_pair(nullptr, nullptr);
}
// release resource and take new resource
void _Resetw0(T *other_ptr, CRefCount *other_rep, _Deleter& func) {
_DecrefWeak();
_ref_count = other_rep;
_ptr = other_ptr;
_deleter = func;
}
//release resource
virtual void _Destroy() noexcept {
if (_deleter.first) {
_deleter.first(_ptr);
}
if (_deleter.second) {
_deleter.second(_ref_count);
}
}
virtual void _DestroyThis() noexcept {
}
virtual ~CBasePtr() {}
protected:
T *_ptr;
CRefCount *_ref_count;
std::pair<_PtrDeleter, _RefDeleter> _deleter;
};
_PtrDeleter 和 _RefDeleter 是兩個函式物件,當引用計數為0時智慧指標呼叫他們釋放從記憶體池中申請的記憶體,一個負責釋放上層應用所使用的記憶體,一個釋放引用計數類指標佔有的記憶體。之後是兩種指標的實現程式碼:
// class for reference counted resource management
template<class T>
class CMemSharePtr : public CBasePtr<T> {
public:
// construct
CMemSharePtr() noexcept : CBasePtr() {}
CMemSharePtr(T* ptr, CRefCount* ref) noexcept : CBasePtr(ptr, ref) {}
CMemSharePtr(T* ptr, CRefCount* ref, _Deleter& func) noexcept : CBasePtr(ptr, ref, func) {}
CMemSharePtr(const _BasePtr& r) : CBasePtr(r) {}
CMemSharePtr(_BasePtr&& r) : CBasePtr(r) {}
CMemSharePtr& operator=(_BasePtr&& r) {
_BasePtr::operator=(r);
return (*this);
}
CMemSharePtr& operator=(const _BasePtr& r) {
_BasePtr::operator=(r);
return (*this);
}
~CMemSharePtr() {
this->_DecrefUse();
}
_BasePtr& operator==(const _BasePtr& r) noexcept {
return _ptr == r._ptr;
}
// return pointer to resource
T *operator->() const noexcept {
return (this->Get());
}
template<typename T2>
T2 *operator->() const noexcept {
return dynamic_cast<T2*>(this->Get());
}
// return pointer to resource
T operator*() const noexcept {
return (*(this->Get()));
}
// return true if no other CMemSharePtr object owns this resource
bool unique() const noexcept {
return (this->UseCount() == 1);
}
// test if CMemSharePtr object owns no resource
explicit operator bool() const noexcept {
return (this->Get() != 0);
}
};
// class for pointer to reference counted resource.
// construc from CMemSharePtr
template<class T>
class CMemWeakPtr : public CBasePtr<T> {
public:
CMemWeakPtr() {
Resetw();
}
CMemWeakPtr(_BasePtr& r) {
Resetw(r);
}
// construct CBasePtr object that takes resource from _Right
CMemWeakPtr& operator=(_BasePtr&& r) {
_BasePtr::operator=(r);
return (*this);
}
// construct CBasePtr object that takes resource from _Right
CMemWeakPtr& operator=(_BasePtr& r) {
Resetw(r);
return (*this);
}
// release resource
~CMemWeakPtr() noexcept {
this->_DecrefWeak();
}
// convert to CMemSharePtr
CMemSharePtr<T> Lock() const noexcept {
if (Expired()) {
return CMemWeakPtr();
}
return (CMemSharePtr<T>(*this));
}
};
我們過載了->操作符和*操作符,來使指標物件使用起來就像是裸指標一樣,這正是過載操作符的魅力所在。這樣一來客戶建立指標的時候,還需要手動的將回收函式複製給指標,不然就不能歸還記憶體。乾脆好人做到底,送佛送到西,再封裝兩個函式來負責構造智慧指標:
template<typename T, typename... Args >
CMemSharePtr<T> MakeNewSharedPtr(CMemaryPool& pool, Args&&... args) {
T* o = pool.PoolNew<T>(std::forward<Args>(args)...);
CRefCount* ref = pool.PoolNew<CRefCount>();
std::pair<std::function<void(T*&)>, std::function<void(CRefCount*&)>> del = std::make_pair(std::bind(&CMemaryPool::PoolDelete<T>, &pool, std::placeholders::_1), std::bind(&CMemaryPool::PoolDelete<CRefCount>, &pool, std::placeholders::_1));
return CMemSharePtr<T>(o, ref, del);
}
template<typename T>
CMemSharePtr<T> MakeMallocSharedPtr(CMemaryPool& pool, int size) {
T* o = (T*)pool.PoolMalloc<T>(size);
CRefCount* ref = pool.PoolNew<CRefCount>();
std::pair<std::function<void(T*&)>, std::function<void(CRefCount*&)>> del = std::make_pair(std::bind(&CMemaryPool::PoolFree<T>, &pool, std::placeholders::_1, size), std::bind(&CMemaryPool::PoolDelete<CRefCount>, &pool, std::placeholders::_1));
return CMemSharePtr<T>(o, ref, del);
}
template<typename T>
CMemSharePtr<T> MakeLargeSharedPtr(CMemaryPool& pool) {
T* o = pool.PoolLargeMalloc<T>();
CRefCount* ref = pool.PoolNew<CRefCount>();
std::pair<std::function<void(T*&)>, std::function<void(CRefCount*&)>> del = std::make_pair(std::bind(&CMemaryPool::PoolLargeFree<T>, &pool, std::placeholders::_1), std::bind(&CMemaryPool::PoolDelete<CRefCount>, &pool, std::placeholders::_1));
return CMemSharePtr<T>(o, ref, del);
}
template<typename T>
CMemSharePtr<T> MakeLargeSharedPtr(CMemaryPool& pool, int size) {
T* o = pool.PoolLargeMalloc<T>(size);
CRefCount* ref = pool.PoolNew<CRefCount>();
std::pair<std::function<void(T*&)>, std::function<void(CRefCount*&)>> del = std::make_pair(std::bind(&CMemaryPool::PoolLargeFree<T>, &pool, std::placeholders::_1, size), std::bind(&CMemaryPool::PoolDelete<CRefCount>, &pool, std::placeholders::_1));
return CMemSharePtr<T>(o, ref, del);
}
到這裡便可以大功告成,配合我們之前所說的記憶體池,讓上層業務既高效的使用了記憶體,又免去了記憶體釋放的擔憂,可謂多快好省乎?
下邊是測試使用程式碼:
#include "PoolSharedPtr.h"
int main() {
CMemaryPool pool(1024, 10);
auto ptr = MakeNewSharedPtr<test1>(pool, 1, 2, 3, 4);
ptr->aaaa = 100;
{
auto weak = CMemWeakPtr<test1>(ptr);
auto ptrtr = weak.Lock();
}
auto ptr2 = MakeMallocSharedPtr<char>(pool, 55);
strcpy(*ptr2, "100000");
auto ptr3 = MakeLargeSharedPtr<char>(pool);
strcpy(*ptr3, "100000");
CMemWeakPtr<char> ptr5;
{
auto ptr4 = MakeMallocSharedPtr<char>(pool, 55);
{
CMemSharePtr<char> ptr2(ptr4);
}
{
CMemSharePtr<char> ptr2 = ptr4;
}
{
ptr5 = ptr4;
}
}
auto ptr6 = CMemSharePtr<char>();
if (ptr6) {
int a = 0;
a++;
}
int a = 0;
a++;
}
Update:
1,使用測試的時候發現std::function複製的時候非常佔用CPU,每次建立智慧指標,傳遞智慧指標的時候都會引起復制。函式物件本質上是一個實現了operator()的類物件,每個智慧指標都持有一份物件實屬不智。所以我在這裡修改了一下,將建立智慧指標時所使用的記憶體池物件指標儲存在智慧指標中,以免不必要的複製引起效能損耗。
2,為了支援shared_from_this,模仿boost新建了一個名稱為CEnableSharedFromThis的類,在這個類中宣告一個CMemWeakPtr成員變數,使繼承了CEnableSharedFromThis的的類可以通過this指標建立智慧指標。
template<class T>
class CEnableSharedFromThis {
public:
typedef T _EStype;
CMemSharePtr<T> memshared_from_this() {
return (_weak_ptr.Lock());
}
protected:
constexpr CEnableSharedFromThis() noexcept {
}
CEnableSharedFromThis(const CEnableSharedFromThis&) noexcept {
}
CEnableSharedFromThis& operator=(const CEnableSharedFromThis&) noexcept {
return (*this);
}
~CEnableSharedFromThis() noexcept {
}
private:
template<class T1, class T2>
friend void DoEnable(T1 *ptr, CEnableSharedFromThis<T2> *es, CRefCount *ref_ptr, CMemaryPool* pool = 0, int size = 0, MemoryType type = TYPE_NEW);
CMemWeakPtr<T> _weak_ptr;
};
在智慧指標的建構函式中為_weak_ptr賦值,但是如何才能區分到底到底有沒有_weak_ptr呢(類有沒有繼承CEnableSharedFromThis)?這裡用到C++11的一個新特性,可以根據編譯期資訊推斷類是否有特定的成員變臉或成員函式(go的低配版反射)
template<typename T>
struct has_member_weak_ptr {
template <typename _T>
static auto check(_T)->typename std::decay<decltype(_T::_weak_ptr)>::type;
static void check(...);
using type = decltype(check(std::declval<T>()));
enum { value = !std::is_void<type>::value };
};
template<class T>
inline void EnableShared(T *ptr, CRefCount *ref_ptr, CMemaryPool* pool, int size, MemoryType type) {
if (ptr) {
if (has_member_weak_ptr<T>::value > 0) {
DoEnable(ptr, (CEnableSharedFromThis<T>*)ptr, ref_ptr, pool, size, type);
}
}
}
在智慧指標的建構函式中呼叫EnableShared即可。
3,添加了執行緒鎖
最後是原始碼地址:
GitHub:https://github.com/caozhiyi/Base