智能指针unique_ptr

        介绍智能指针前先看这么段代码,定义一个类:

class A
{
    private:
        int b;
    public:
        A(int c):b(c) { cout << "call constructor..." << endl;}
        ~A() { cout << "call destructor..." << endl;}
        int getValue() { return b;}
};

        主方法中某代码块代码如下:

{
    A *pa = new A(3);
    cout << "pa.b:" << pa->getValue() << endl;
}

        程序执行到代码块结束时,并没有执行 delete 运算符。

(gdb) n
call constructor...
21              cout << "pa.b:" << pa->getValue() << endl;
(gdb) p pa
$1 = (A *) 0x100304100
(gdb) p &pa
$2 = (A **) 0x7ffeefbff9a0
(gdb) n
pa.b:3
26              unique_ptr<A> ua (new A(4));
(gdb) p pa
No symbol "pa" in current context.
(gdb) p *(A **)0x7ffeefbff9a0
$3 = (A *) 0x100304100
(gdb) p **(A **)0x7ffeefbff9a0
$4 = {b = 3}

        由上可以看到即使程序已跳出代码块,尽管指针 pa 已不存在,但内存地址 0x7ffeefbff9a0 指向的仍是堆空间地址 0x100304100,即发生了内存泄漏。

        这只是人为因素没执行 delete 语句,但实际开发中还有可能因中间程序异常而导致没执行后面的 delete 语句,这样也会引发内存泄漏。由此便提出了智能指针,使得智能指针可以像类对象一样,当指针本身过期时,就会调用析构函数释放其指向的堆空间。这里我们仅介绍 unique_ptr

{
    unique_ptr<A> ua (new A(4));
    cout << "ua.b:" << ua->getValue() << endl;
}

        gdb 调试如下:

(gdb) p ua
$9 = {__ptr_ = {<std::__1::__compressed_pair_elem<A*, 0, false>> = {
      __value_ = 0x100304110}, <std::__1::__compressed_pair_elem<std::__1::default_delete<A>, 1, true>> = {<std::__1::default_delete<A>> = {<No data fields>}, <No data fields>}, <No data fields>}}
(gdb) p &ua
$10 = (std::__1::unique_ptr<A, std::__1::default_delete<A> > *) 0x7ffeefbff988
...
(gdb) p ua
No symbol "ua" in current context.
(gdb) p *(A **)0x7ffeefbff988
$11 = (A *) 0x0

        可以看到当程序跳出代码块时,不仅智能指针 ua 已过期,其指向的内容也得已释放,也就避免了内存泄漏。那么它是怎么做到的呢?可以看下 memory 头文件中的定义:

template <class _Tp, class _Dp = default_delete<_Tp> >
class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr {
public:
    ...
    ~unique_ptr(void);
    void reset(pointer);
    ...
private:
  __compressed_pair<pointer, deleter_type> __ptr_;
    ...

}

        它是一个模板类,有两个参数分别为 _Tp_Dp

  • _Tp表示原生指针的类型。

  • _Dp则表示析构器,开发者可以自定义指针销毁的代码。其拥有一个默认值default_delete<_Tp>,其实就是标准的delete函数。

        还有一个析构函数,实现如下:

~unique_ptr() { reset(); }

void reset(pointer __p = pointer()) _NOEXCEPT {
    pointer __tmp = __ptr_.first();
    __ptr_.first() = __p;
    if (__tmp)
      __ptr_.second()(__tmp);
}

        这里的 reset() 方法没接收任何参数,也就释放了当前指向的堆内存,其中的 __ptr_ 就是 unique_ptr 的数据成员,实为原生指针和析构器的 pair

template <class _T1, class _T2>
class __compressed_pair : private __compressed_pair_elem<_T1, 0>,
                          private __compressed_pair_elem<_T2, 1> {
public:
    ...
    typedef _LIBCPP_NODEBUG_TYPE __compressed_pair_elem<_T1, 0> _Base1;
    typedef _LIBCPP_NODEBUG_TYPE __compressed_pair_elem<_T2, 1> _Base2;
    ...
    __compressed_pair(_U1&& __t1, _U2&& __t2)
     : _Base1(_VSTD::forward<_U1>(__t1)), _Base2(_VSTD::forward<_U2>(__t2)) {}
    ...
    typename _Base1::reference first() _NOEXCEPT {
        return static_cast<_Base1&>(*this).__get();
    }
    ...
    typename _Base2::reference second() _NOEXCEPT {
        return static_cast<_Base2&>(*this).__get();
    }
    ...
}

        类 __compressed_pair 私有继承结构体 __compressed_pair_elem

template <class _Tp, int _Idx,
          bool _CanBeEmptyBase =
              is_empty<_Tp>::value && !__libcpp_is_final<_Tp>::value>
struct __compressed_pair_elem {
  typedef _Tp _ParamT;
  typedef _Tp& reference;
  typedef const _Tp& const_reference;
  ...
  __compressed_pair_elem(_Up&& __u)
      : __value_(_VSTD::forward<_Up>(__u))
    {
    }
  ...
  _LIBCPP_INLINE_VISIBILITY reference __get() _NOEXCEPT { return __value_; }
  _LIBCPP_INLINE_VISIBILITY
  const_reference __get() const _NOEXCEPT { return __value_; }

private:
  _Tp __value_;
};

        这样通过 first() 方法就能获取到原生指针,通过 second() 获取 delete 函数。这点也应用到智能指针 unique_ptr 的其它成员函数中,具体实现可在 memory 头文件中查看。

        另外 unique_ptr 接收同类型的构造函数有以下两种:

// 移动拷贝函数
unique_ptr(std::__1::unique_ptr<_Tp, _Dp> &&);
// 移动赋值函数
std::__1::unique_ptr<_Tp, _Dp> & operator=(std::__1::unique_ptr<_Tp, _Dp> &&);

        可以看到两个构造函数接收的参数都只能是右值,所以如果参数本身就是 unique_ptr,需要对其类型转换为右值后才可进行赋值或拷贝,譬如执行 std::move() 方法。这也是 unique_ptr 与其它智能指针的不同之处,保证了对于一个特定的对象,只能有一个智能指针指向它。

        本文部分参考于🔗C++ 智能指针最佳实践&源码分析

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值