从去年(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,就这样,一个小轮子就造成了。