自己实现 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,就这样,一个小轮子就造成了。





你可能感兴趣的:(C++,shared-ptr)