智能指针与定制删除器

定制删除器

前面我们的智能指针就是:

  1. RAII。

  2. 像指针一样。

但是我们的智能指针的析构函数就只是 delete。

// 这里简单的看一下 shared_ptr 就知道了
        ~shared_ptr()
        {
            if (--(*_pcount) == 0)
            {
                delete _ptr;
                delete _pcount;
            }
        }

那么如果我们想要 new[] 呢? 或者既然是 RAII 的思想,那么就应该其他的资源也可以管理,就比如说是 malloc 或者是文件文件描述符等...

所以我们还是需要定制删除器,也就是定制一个我们自己的 delete。

而定制删除器实际上也就是一个可调用对象,既可以是函数指针,也可以是仿函数,亦或者是 lambda 表达式。

我们现在看一下没有写定制删除器的智能指针,如果 new[] 会怎么办?

  • 首先,我们前面说过 new 与 delete 搭配使用,new[] 与 delete[] 搭配使用。

void test8()
{
    lxy::shared_ptr<A> Pa(new A[3]);
}

结果:

构造函数:A(int a = 0)
构造函数:A(int a = 0)
构造函数:A(int a = 0)
析构函数:~A()
  • 这里看到 A 的构造函数被调用的3次,说明全部都被构造好了。

  • 但是析构函数值调用的了一次,因为 shared_ptr 默认就是 delete,所以只调用了一次析构,才会导致奔溃。

下面我们就可以写我们的定制删除器,那么我们想要该类再析构的时候调用可调用对象来析构,那么当然是我们再构造的时候,或者是模板的时候就传进入一个仿函数,或者是再构造函数的时候再传入可调用对象,但是STL 库里面是再构造函数的时候传入的,所以我们也在构造函数的时候传入。

但是,如果是我们仅仅传入的话,那么当析构的时候我们不知道去那里找这个可调用对象,但是我们可以将他保存起来。

那么我们如何保存呢?我们怎么知道传进来的是一个函数,还是仿函数,还是 lambda 表达式。

其实我们可以巧妙的利用C++11 的包装器!!

我们可以给 shared_ptr 中添加一个成员变量,也就是用来记录定制删除器的变量,那么我们可以将该变量声明为一个包装器类型的一个变量,那么我们如何声明呢?

  • 因为我们再删除的时候,由于不知道 T* _ptr 是什么类型的,如果是 new[] 那么我们就需要用 delete[] 如果是 malloc 我们就需要 free ,如果是 FILE 那么我们就需要 close 所以我们可以将该函数传入一个 ptr 的指针,也就是需要管理的资源的指针,目的就是释放该管理的资源,而 shared_ptr 里面还有一个 int* 的计数器,该计数器不需要我们来管理,因为这个计数器他本身就是一个 new 出来的,我们只需要将其 delete 即可,而我们真正需要管理的就是我们想要让 shared)ptr 为我们管理的资源。

function<void(T* ptr)> _del = [](T* ptr) {delete ptr; };
  • 这里就是该成员变量的声明,我们可以在构造函数的时候给他一个定制删除器。

下面我们就改写一下 shared_ptr 改写一些并不难。

我们只需要为 shared_ptr 类添加一个构造函数,也就是可以传入定制删除器的构造函数。

        shared_ptr(T* ptr, function<void(T* ptr)> del)
            :_ptr(ptr)
            , _pcount(new int(1))
            , _del(del)
        {}
  • 也就是我们为该类添加一个这样的函数,但是为了方便,我买也可以直接为该构造函数添加一个模板。

        template<class D>
        shared_ptr(T* ptr, D del)
            :_ptr(ptr)
            , _pcount(new int(1))
            ,_del(del)
        {}
  • 我们可以写成这样。这里两种都是可以的。

那么上面这样改写就算结束了吗?

当然没有,我们先看下面的这段代码。

void test8()
{
    auto del = [](A* ptr) {delete[] ptr; };
​
    lxy::shared_ptr<A> Pa(new A[3], del);
    lxy::shared_ptr<A> Pa1(Pa);
​
}
  • 我们先看这样会不会报错。

结果:

构造函数:A(int a = 0)
构造函数:A(int a = 0)
构造函数:A(int a = 0)
析构函数:~A()
析构函数:~A()
析构函数:~A()

这样我们是没有问题的,即使我们拷贝了也没有问题,但是这样真的就没有问题吗?

我们拷贝后,拷贝的对象里面的 _del 成员变量是什么,是默认的 delete 所以我们还是需要为拷贝和赋值都添加一个。

这里为了验证我们所说的是正确的,我们可以看一下拷贝后失败的案例,但是上面为什么拷贝后还是成功的呢?

因为先创建的后析构,所以这里是因为 Pa1 先析构,但是 Pa1 里面的计数器减减后并没有到0,所以没有析构,而有计数器的那个对象是后析构的,所以他析构的时候,shared_ptr里面的 T* _ptr 析构会调用我们写的定制删除器,所以没有办法,那么我们要怎么让拷贝的对象会是最后一次析构呢?我们可以把一个对象赋值给Pa 这样 Pa 所管理的资源的引用计数就会减减,而这份资源的最后一次管理权也就会交到 Pa1 手中,所以只需要等Pa1 析构的时候就是这份资源释放的时候,而这份资源是 new[] 出来的,而 Pa1 里面的定制删除器是,默认的也就是简单的 delete 所以此时析构的话就会报错。

void test8()
{
	auto del = [](A* ptr) {delete[] ptr; };

	lxy::shared_ptr<A> Pa(new A[3], del);
	lxy::shared_ptr<A> Pa1(Pa);
	lxy::shared_ptr<A> Pa2(new A[5], del);
	Pa = Pa2;
}
  • 这样 Pa2 给 Pa1 此时 Pa1 手中的那一份资源的管理全就会减减,然后只剩下 Pa1 手中的一份。

  • 所以再 Pa1 析构的时候就会报错。

结果:

构造函数:A(int a = 0)
构造函数:A(int a = 0)
构造函数:A(int a = 0)
构造函数:A(int a = 0)
构造函数:A(int a = 0)
构造函数:A(int a = 0)
构造函数:A(int a = 0)
构造函数:A(int a = 0)
析构函数:~A()
  • 这里我们看到了 只析构了一次。

那么为什么赋值也要给 del 也赋值呢?

  • 因为再赋值的时候,如果一个new[] 需要用 delete[] 释放,而一个是 new 只需要 delete 就可以释放,所以如果不将定制删除其也赋值的话,那么就会出现问题。

  • 上面这个的报错我们就不演示了。

其实有了定制删除器后,我们可以染发 shared_ptr 管理其他的资源,就不光光管理指针的,真正的实现了 RAII 的思想。

我们将打开的文件指针交给 shared_ptr 进行管理:

void test9()
{
	auto del1 = [](FILE* fd) {fclose(fd); };
	lxy::shared_ptr<FILE> Pf(fopen("test.txt", "w"), del1);
	fprintf(Pf.getPtr(), "%s", "hello world");
}
  • 这里为了支持我们的写入,我们需要提供一个 getPtr 因为我们写入的时候,需要文件指针,而我们需要提供getPtr 或者是将 _ptr 放成公有,才可以,这里选择提供getPtr。

结果:

hello world

这就是文件里面写入的内容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Naxx Crazy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值