從去年(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
。對於成員refCount
和 reference
,他們必須放在父類中。因爲在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,就這樣,一個小輪子就造成了。