自己實現 SharedPtr(1) —— 管理 Deleter

從去年(2015年)開始,就斷斷續續看過不少C++相關的書,可是由於個人的一些壞習慣,卻一直都沒怎麼的用過。以至於學了忘,忘了學。所以呢,現在就想着自己造一下輪子,練練手。折騰了一個上午,也就有了這麼一篇文章。

首先,模板是標配,所以就有了下面的定義

template<typename Reference>
class SharedPtr {
};


對於一般的使用場景,我們喜歡是有默認的delete ptr,又可以自定義 deleter。對於默認的deleter,實現起來其實很簡單:

template<typename Pointer>
class PointerDeleter {
public:
    void operator()(Pointer pointer) {
        delete pointer;
    }
};


爲了支持自定義的 deleter,我們有下面的兩種選擇:

template<typename Reference>
class SharedPtr {
public:
    SharedPtr(Reference r);

    template<typename Deleter>
    SharedPtr(Reference r, Deleter deleter);
};

// or

template<typename Reference, typename Deleter>
class SharedPtr {
public:
    SharedPtr(Reference r);
    SharedPtr(Reference r, Deleter deleter);
};

對於第二種,我們直接添加一個模板參數,這對於實現工作來說,肯定會輕鬆很多,但是卻不易於用戶使用。而第一種呢,通過類型推斷,對用戶會更加的友好。事實上,第一種也是 C++ std 庫所選擇的方式。於是,這裏我選擇的是第一種。


接下來是定義 SharedPtr 內部的數據。同樣的,有兩種選擇

template<typename Reference>
class SharedPtr {
private:
    int* refCount;
    Reference reference;
    Deleter?? deleter;    // Oops!!
};

// or

template<typename Reference>
class SharedPtr {
private:
    struct SharedData {
        int refCount;         // not a pointer
        Reference reference;
        Deleter?? deleter;    // Oops!!
    };

    SharedData* mSharedData;
};

既然兩種都需要使用指針,那麼,其實我們並沒有太多理由選擇第一種。

此外,這裏有一個比較棘手的問題——deleter 的類型該怎麼聲明?

對於我們的deleter,他就是一個返回值爲void,參數爲 Reference的一個functor,所以,第一種解法是,利用C++ <functional>中的 std::function

struct SharedData {
    int refCount;
    Reference reference;
    std::function<void(Reference)> deleter;
};

不僅如此,我們還不需要對構造函數進行模板化:

template<typename Reference>
class SharedPtr {
public:
    SharedPtr(Reference r);
    SharedPtr(Reference r, std::function<void(Reference)> d);
};

但是,現在是在造輪子啊,誰要用你標準庫的東西!(話雖然是這麼說,但其實,不少地方,其實還是用了的)


爲了支持不同類型的 functor,這裏我們只能對 SharedData模板化:

template<typename Deleter>
struct SharedData {
    int refCount;
    Reference reference;
    Deleter deleter;
};

然後,我們聲明 SharedPtr的成員:

template<typename Reference>
class SharedPtr {
private:
    SharedData<???>* mSharedData;  // my goodness!
};

好吧,這個是最令人頭痛的問題了,我們要給mSharedData什麼樣的模板參數纔好啊?

不給。拜託,這又不是Java

既然我們只是想存一個指針,乾脆用 void *。等等,那使用他的時候,要把他cast成什麼樣的類型好啊??

指針?這個應該會是個不錯的方向。我們現在需要的,是用一個指針,他可以指向很多相似類型的對象。等等,你說很多相似的對象?那不就是繼承了嗎?父類指針可以指向他的子類,並且,利用虛函數,也可以讓刪除這個行爲根據特定的類型發生變化。

完美!

class SharedData {
public:
    int refCount = 1;
    Reference reference;
    virtual ~SharedData() {}
};

template<typename Deleter>
class SharedDataImpl: public SharedData {
public:
    Deleter deleter;
    SharedDataImpl(Reference r, Deleter d)
        : SharedData{r}, deleter{d} {}

    ~SharedDataImpl() {
        deleter(SharedData::reference);
    }
};

這裏,我們把刪除的行爲放到了子類裏,如此一來,便可以不指定具體的Deleter,同時又可以接受任意類型的deleter。對於成員refCountreference,他們必須放在父類中。因爲在SharedPtr裏,我們所存儲的僅僅是SharedData的指針,所以,如果他們放在子類中,我們將無法訪問這兩個字段。
同時,雖然子類的名字不太好看,但是,就邏輯上而已,這樣的數據分佈也是說得通的:既然父類叫 shared data,我們自然可以期望在從他那裏得到 the shared data 。而對於子類的存在,僅僅是爲了多態地刪除資源,所以,把deleter放在子類裏,也是很合理的。

下面是完整的代碼(copy/move 語義尚未實現)


#ifndef LIBS_UTIL_SHARED_PTR_H_
#define LIBS_UTIL_SHARED_PTR_H_

#define DEBUG_SHARED_PTR

#if defined(DEBUG_SHARED_PTR)
#include <iostream>
#endif



namespace jl_util {


template<typename Reference>
class SharedPtr {
public:
    SharedPtr(Reference r)
        : SharedPtr{r, DefaultDeleter{}} {
    }

    template<typename Deleter>
    SharedPtr(Reference r, Deleter d)
        : mSharedData{new SharedDataImpl<Deleter>{r, d}} {
    }

    ~SharedPtr() {
        if (--mSharedData->refCount == 0) {
            delete mSharedData;
#if defined(DEBUG_SHARED_PTR)
            std::cout << "~SharedPtr(): resource destroyed"
                      << std::endl;
        }
#endif
}


private:
    template<typename Pointer>
    class PointerDeleter {
    public:
        void operator()(Pointer pointer) {
            delete pointer;
        }
    };

    using DefaultDeleter = PointerDeleter<Reference>;

    class SharedData {
    public:
        int refCount = 1;
        Reference reference;

        virtual ~SharedData() {
        }

    protected:
        SharedData(Reference r) : reference{r} {
        }
    };

    template<typename Deleter>
    class SharedDataImpl: public SharedData {
    public:
        Deleter deleter;

        SharedDataImpl(Reference r, Deleter d)
            : SharedData{r}, deleter{d} {
            }

        ~SharedDataImpl() {
            deleter(SharedData::reference);
        }

    };

    SharedData* mSharedData;
};

}
#endif


// *******************************************************
// test

#include <iostream>
using namespace std;

class Tester {
public:
    Tester() {}
    ~Tester() {
        cout << "destroy Tester" << endl;
    }
};

void testDestroy() {
    jl_util::SharedPtr<Tester *> sharedPtr(new Tester);
    jl_util::SharedPtr<Tester *>
        sharedPtr2(new Tester,
                   [](Tester* p) {
                       cout << "deleted Tester" << endl;
                       delete p;
                   });
}

int main() {
    testDestroy();
}




但是,故事並沒有到此結束。等我把上面最後一段代碼粘上去,我才發現,他不是異常安全的!!好吧,心都碎了,寫了那麼久,居然在最後關頭出了岔子。

其實……也沒有那麼嚴重。我們所要做的,就是在分配SharedDataImpl的時候,如果分配失敗,就直接回收資源,然後重新拋出異常。

#include <new>

// ...

template<typename Deleter>
SharedPtr(Reference r, Deleter d) try
    : mSharedData{new SharedDataImpl<Deleter>{r, d}} {
} catch (std::bad_alloc) {
    d(r);
    throw;
}
//...

OK,就這樣,一個小輪子就造成了。





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章