1. 程式人生 > >C++智慧指標實現

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智慧指標的實現,實現了基於記憶體池的智慧指標和弱指標,從而避免了再手動釋放記憶體給記憶體池的步驟。這裡我們要實現的功能是將從記憶體池中獲取到的記憶體交給一個類的物件來管理,這個物件保持一個關於這個記憶體的引用者的引用計數,當計數為零時將這塊記憶體還給記憶體池。
首先我們定義一個管理引用計數的類,把引用計數和指標管理的類分開來存放,這個概念在

《C++沉思錄》中有很詳細的講解(這本書把面向物件程式設計講解的極其細緻,很多東西使人有醍醐灌頂之感),這個類只負責維護兩個引用的計數,智慧指標計數和弱指標計數,為了支援多執行緒,我們將這兩個成員變數定義為原子型別。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