在c++primer16.1这一节有这样的题目:
让我们实现自己的shared_pr和unique_ptr模板
如果没有删除器的传入,那么这两个模板的实现并不算难,shared_ptr共享指针,只需要为它多申请一块内存用来存放引用次数就可以了。
但是在这一节介绍了shared_ptr和unique_ptr传入删除器的不同点,如果要加入删除器的传入,那就是个很大的挑战了。
首先的问题,我们应该如何储存传入的删除器?
对于shared_ptr,我做了几个测试,传入的删除器要有以下的特点:
1.传入的删除器参数只能有一个(析构函数会对指向的调用我们传入的删除器)
2.模板实参类型的指针必须可以转换成为传入的删除器的参数(比如指向string,那么传入的删除器的参数应该是string*)
除此之外便没有什么别的限定了,甚至于,删除器的返回类型可以是int而不是void。
而且当我们运行时候才知道删除器的具体类型,我们可以向他传入函数对象,也可以传入函数指针,我们可以通过reset来改变他的删除器类型
这意味着删除器可能并不是一个成员,而是一个类似于指针的东西。因为直接作为成员是无法转换类型的。不过可以存放函数对象,又可以存放函数指针的东西又有什么?
刚开始我用了void指针,但是有下面两个问题:
1.的确它可以指向任何指针,但是它似乎不能指向函数对象。我们传入函数对象时,可能必须使用取址符&,这不是想要的结果。
2.为了使用void指针指向的函数,我们必须对他进行类型转换,否则无法调用。而进行了强制转换后,又有一个问题,即使我们传入的指针完全不符合上述两点要求,
它也会转换而不会报错。当然这样的行为是未定义的。
这时候前两天看到的叫做function的标准库类型便显得比较有用了。它可以储存函数指针,函数对象,lambda表达式,只要他们的调用方式相同。虽然依然有问题,比如返回值的问题,我们在一开始就给他了参数void(T*),那么返回值非int的就不能传入,但是相对于之前,这也是很大的进步了。
下面写一下用function的源码
#pragma once
#include<functional>
using std::function;
template <typename T> class make_shared;
template <typename T>
class shared_ptr
{
typedef function<void(T*)> deletype;
friend class make_shared<T>;
public:
shared_ptr() = default;
shared_ptr(T* t,deletype de = [](T*a) {delete a; }) :data(t), refer_time(new size_t()),dele(de) { ++*refer_time; }
shared_ptr(const shared_ptr<T> &t):data(t.data), refer_time(t.refer_time), dele(t.dele) { ++*refer_time; }
shared_ptr(const shared_ptr<T> &t, deletype de) :data(t.data), refer_time(t.refer_time), dele(de) { ++*refer_time; }
~shared_ptr() { free(); }
void reset() { data = nullptr; --*refer_time; refer_time = nullptr; dele = [](T*a) {delete a; }; }
void reset(T*t, deletype de = [](T*a) { delete a; })
{
free();
data = t;
refer_time = new size_t();
++*refer_time;
dele = de;
}
T& operator*()
{
return *data;
}
shared_ptr &operator = (const shared_ptr<T> & sp)
{
++*(sp.refer_time);//为了支持自赋值,我们必须先这样,否则内存可能提前释放
free();
data = sp.data;
refer_time = sp.refer_time();
dele = sp.dele;
}
private:
T* data=nullptr;//指针指向的
size_t * refer_time = nullptr;//引用次数
deletype dele;//保存删除器
void free();
};
template <typename T> void shared_ptr<T>::free()
{
if (!(--*refer_time))
{
delete refer_time;
dele(data);
}
}
在上述代码中,如果没有传入删除器,我们将调用一个lambda表达式,毕竟我们不能像判断指针是否为空一样判断function对象
当然我们可以通过判断function是否是空,然后来决定是否应该调用delete来删除,不过在本质上这和用lambda表达式是没有区别的
值得注意的是 我们在类内每次都要把lambda表达式打出来,而不能用auto f = [](T*a){delete a;}然后每次写 等于 f就行,因为在类内它会被当作一个成员。
上述基本是这样,但是我们依然无法传入int型的,这个我也不清楚,似乎要用到类型擦除。或者可能用到any类,这个以后再说
对于unique_ptr模板来说,虽然他是指针,但是他独享他指向的对象,这意味着它不能拷贝,但是是支持移动赋值等操作的。所以他也没有引用次数等等一说
这样他的这些部分的实现可能比shared_ptr更简单。
对于删除器,我们要像unique_ptr类型传入删除器,就要在声明时候显示传入删除器的类型。
如果我们要显示传入删除器,这固然是很容易实现的,不过他的实现难在默认的部分。
因为上述原因,所以我们知道unique_ptr的模板有两个参数。其中第二个参数是有默认参数
那他的默认参数应该是什么?是指向函数的指针吗?
因为删除器是类的一部分,所以即使默认的他也应该存在,我们可以直接调用dele,而不用做什么判断。