C++スマートポインタ実装


前回のメモリプールについて説明した記事では、メモリプールベースのスマートポインタについて説明しました.C++はGCメカニズムがなく、プログラマー自身がメモリを管理する必要があり、スマートポインタはC++プログラマーがメモリを管理する利器である.スマートポインタの原理は広く知られています通俗的にはクラスでポインタを表す(クラスでオブジェクトがOOP思想の核心であることを表す)、メンバー関数にオブジェクトを指すポインタを構築し、コンストラクション関数、付与関数、レプリケーションコンストラクション関数、解析関数などでオブジェクトの個数を変える場所で参照カウントを増加または減少させ、参照カウントが0の場合、ポインタが指すメモリを解放し、プログラマの手動解放を回避し、メモリの漏洩と野針の危険.C++11はスマートポインタと弱いポインタを導入し,符号化を極めて便利にした.弱いポインタとは?弱いポインタのオブジェクトは、このメモリを指すポインタを持っていますが、参照カウントは増加しません.なぜこのメカニズムを追加するのですか?スマートポインタは完璧に見えますが、致命的な欠陥があります.
class A;
class B {
     
public:
    std::shared_ptr _a_ptr;
    ~B() { std::cout << "BBB" << std::endl; }
};

class A {
     
public:
    std::shared_ptr _b_ptr;
    ~A() { std::cout << "AAA" << std::endl; }
};

int main() {
    {
        std::shared_ptr a(new A);
        std::shared_ptr b(new B);
        a->_b_ptr = b;
        b->_a_ptr = a;
    }
    int a = 0;
    a++;
}

スマートポインタが循環して参照されると、メモリが解放されません!!これが弱いポインタが登場するチャンスだ.
class A {
     
public:
    std::weak_ptr _b_ptr;
    ~A() { std::cout << "AAA" << std::endl; }
};

クラスの1つのポインタタイプを弱いポインタに変更すれば、占有メモリを効率的に解放できます.ただし、弱いポインタを使用する場合は、スマートポインタに変換して使用する必要があります.私はここでC++11スマートポインタの実現を参考にして、メモリプールに基づくスマートポインタと弱いポインタを実現して、それによって再び手動でメモリをメモリプールに解放するステップを避けました.ここで実装する機能は、メモリプールから取得したメモリをクラスのオブジェクトに渡して管理することです.このオブジェクトは、メモリに関する参照者の参照カウントを保持し、カウントがゼロの場合にメモリプールに返します.まず、参照カウントを管理するクラスを定義し、参照カウントとポインタ管理のクラスを分けて保存します.このコンセプトは『C++沈思録』で詳しく説明されています(この本はオブジェクト向けのプログラミング解説を極めて細かく、多くのものが醍醐味を感じさせる)このクラスでは、2つの参照のカウント、スマートポインタカウント、弱いポインタカウントのみを維持し、マルチスレッドをサポートするために、この2つのメンバー変数を原子タイプとして定義しています.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;
};

次に、ポインタのベースクラスを実現し、2つのポインタが共有する操作を実現します.
// base class for CMemSharePtr and CMemWeakPtr
template<typename T>
class CBasePtr {
public:
    typedef CBasePtr                 _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は2つの関数オブジェクトで、参照カウントが0の場合、スマートポインタはメモリプールから申請されたメモリを解放するように呼び出し、上位アプリケーションで使用されているメモリを解放し、参照カウントクラスポインタが占有しているメモリを解放します.次に、2つのポインタの実装コードが表示されます.
// class for reference counted resource management
template<class T>
class CMemSharePtr : public CBasePtr {   
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(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 {
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 Lock() const noexcept {
        if (Expired()) {
            return CMemWeakPtr();
        }
        return (CMemSharePtr(*this));
    }
};

->オペレータと*オペレータをリロードし、ポインタオブジェクトを裸のポインタのように使用するのがリロードオペレータの魅力です.これにより、お客様がポインタを作成するときに、回収関数をポインタに手動でコピーする必要があります.そうしないと、メモリを返すことができません.いっそ良い人が最後までやり、仏を西に送り、2つの関数をカプセル化して知能ポインタを構築します.
templateT, typename... Args >
CMemSharePtr<T> MakeNewSharedPtr(CMemaryPool& pool, Args&&... args) {
    T* o = pool.PoolNew<T>(std::forward(args)...);
    CRefCount* ref = pool.PoolNew<CRefCount>();
    std::pair::function(T*&)>, std::function(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);
}

templateT>
CMemSharePtr<T> MakeMallocSharedPtr(CMemaryPool& pool, int size) {
    T* o = (T*)pool.PoolMalloc<T>(size);
    CRefCount* ref = pool.PoolNew<CRefCount>();
    std::pair::function(T*&)>, std::function(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);
}

templateT>
CMemSharePtr<T> MakeLargeSharedPtr(CMemaryPool& pool) {
    T* o = pool.PoolLargeMalloc<T>();
    CRefCount* ref = pool.PoolNew<CRefCount>();
    std::pair::function(T*&)>, std::function(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);
}

templateT>
CMemSharePtr<T> MakeLargeSharedPtr(CMemaryPool& pool, int size) {
    T* o = pool.PoolLargeMalloc<T>(size);
    CRefCount* ref = pool.PoolNew<CRefCount>();
    std::pair::function(T*&)>, std::function(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(pool, 1, 2, 3, 4);
    ptr->aaaa = 100;

    {
        auto weak = CMemWeakPtr(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()を実現したクラスオブジェクトであり,各スマートポインタが1つのオブジェクトを持つのは賢明ではない.そこでここで修正して、不要なレプリケーションによるパフォーマンス損失を回避するために、スマートポインタを作成するときに使用するメモリプールオブジェクトポインタをスマートポインタに保存します.2,shared_をサポートするためにfrom_thisは、boostに倣ってCEnableSharedFromThisという名前のクラスを新設し、このクラスでCMemWeakPtrメンバー変数を宣言し、CEnableSharedFromThisを継承したクラスがthisポインタによってスマートポインタを確立できるようにした.
template<class T>
class CEnableSharedFromThis {
public:
    typedef T _EStype;
    CMemSharePtr 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 *es, CRefCount *ref_ptr, CMemaryPool* pool = 0, int size = 0, MemoryType type = TYPE_NEW);
    CMemWeakPtr _weak_ptr;
};

インテリジェントポインタのコンストラクション関数では_weak_ptrは値を割り当てますが、どのようにしてあるかどうかを区別することができます.weak_ptrは?ここで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()));
    enum { value = !std::is_void::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::value > 0) {
            DoEnable(ptr, (CEnableSharedFromThis*)ptr, ref_ptr, pool, size, type);
        }
    }
}

インテリジェントポインタのコンストラクション関数でEnableSharedを呼び出せばよい.3,スレッドロックが追加された最後はソースアドレス:GitHub:https://github.com/caozhiyi/Base