关于shared_ptr模板和unique_ptr模板的实现

来源:互联网 发布:洛克人网络争霸战01 编辑:程序博客网 时间:2024/06/08 10:03

     在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,而不用做什么判断。


0 0
原创粉丝点击