当前位置: 代码迷 >> 综合 >> C++11 智能指针 unique_ptr,让资源管理更简单,更安全
  详细解决方案

C++11 智能指针 unique_ptr,让资源管理更简单,更安全

热度:34   发布时间:2023-12-22 02:44:03.0
  1. 总览

    • 轻量级

      • 行为基本和原始指针相同.
    • 大小

      • 默认:delete析构,大小和原始指针一样.
    • 实现

      • 支持构造和移动,不支持拷贝.
      • 数据采用tuple组合,组合类之间采用继承的方式.
    • 析构

      • 默认和自定义.
    • 使用

      • 工厂.
      • 不完整的成员指针变量.
    • 转换

      • unique_ptr独占.
      • 移动转化为shared_ptr也是可以的.
  2. 轻量级

    • 默认

      • 使用delete析构.

      • 大小和原始指针一样.

    • 分析

      template <typename _Tp, typename _Dp = default_delete<_Tp>>
      class unique_ptr
      
      • 可以看到default_delete是默认的析构.

      template<typename _Tp>
      struct default_delete
      {
                constexpr default_delete() noexcept = default;void operator()(_Tp* __ptr) const{
                delete __ptr;}
      };
      
      • 默认析构是一个可调用对象.无数据.

      
      template <typename _Tp, typename _Dp>
      class __uniq_ptr_impl
      {
                
      private:tuple<pointer, _Dp> _M_t;
      };__uniq_ptr_impl<_Tp, _Dp> _M_t;
      
      • unique_ptr的唯一成员变量,_M_t.

      • 实际是tuple的封装,tuple则是以继承的形式实现.

      • 所以默认delete是空类,指针继承还是空的.大小不变,多出来了一些代码而已.

    • 使用场景

      • 既然大小和原始指针一样,那么原始指针可以用的地方,这个也可以.

    • 总结

      • 对指针的封装.

      • 其他的功能都是成员函数实现.

      • 成员函数则是普通函数,第一参数是对象本身而已.

  3. 大小

    • 说明

      • 前面介绍了数据采用继承的形式。

      • 指针的大小是不变的,那么唯一变化的就是析构类.

    • 析构类

      • 可以是lambda,可调用对象,其他.

      • 入参类型和数据指针类型一致即可.

    • tuple

      template<std::size_t _Idx, typename _Head>
      struct _Head_base<_Idx, _Head, false>
      {
                _Head _M_head_impl;
      };template<std::size_t _Idx, typename _Head>
      struct _Head_base<_Idx, _Head, true>
      {
                };template<std::size_t _Idx, typename _Head, typename... _Tail>
      struct _Tuple_impl<_Idx, _Head, _Tail...>: public _Tuple_impl<_Idx + 1, _Tail...>,private _Head_base<_Idx, _Head>
      {
                };
      
      • 空类无成员变量,非空类有成员变量.

      • 模板元变成.智能代码生成器.

  4. 实现

    • 说明

      • 实现了构造和移动。

      • 拷贝被delete掉了.

    • 分析

      
      // Disable copy from lvalue.
      unique_ptr(const unique_ptr&) = delete;
      unique_ptr& operator=(const unique_ptr&) = delete;
      
      • 可以看到拷贝构造和拷贝赋值都被delete.

      • 参与匹配,但是报错.

    • 移动

      
      void reset(pointer __p = pointer()) noexcept
      {
                using std::swap;swap(_M_t._M_ptr(), __p);if (__p != pointer())get_deleter()(__p);
      }pointer release() noexcept
      {
                pointer __p = get();_M_t._M_ptr() = pointer();return __p;
      }unique_ptr& operator=(unique_ptr&& __u) noexcept
      {
                reset(__u.release());get_deleter() = std::forward<deleter_type>(__u.get_deleter());return *this;
      }
      
      • 先对__u执行release,即返回并修改为空,返回值.

      • 在执行reset进行交换并释放原值(交换出来的 __p ).

      • 赋值传递了数据和析构对象.

  5. 使用

    • 工厂类

      • 创建一个成员并返回.

    • 须知

      • 返回时,局部变量变成右值.

      • 然后外部接收对象调用右值构造.

    • 移动

      • 移动代表着资源转移.

      • 原始资源的释放.

    • 交换

      • 互换,不涉及释放.

    • 转移失败

      • 析构.
    • 析构失败

      • signal崩溃.
      • abortunwind stack.
  6. 析构

    • 回顾

      • 默认用delete.

      • 可以自定义析构.

    • 自定义析构

      • 由于采用tuple,数据之间采用继承的方式组合.
      • 所以可以减少空类占用数据.
    • 注意

      • 默认是指针,大小固定.
      • 加了自定义函数可能会增大.
  7. 拓展

    • 数组

      • 需要以数组的形式创建.

      #include<memory>
      #include<iostream>
      class D{
                
      public:D(){
                std::cout << __FUNCTION__ << std::endl;}~D(){
                std::cout << __FUNCTION__ << std::endl;}
      };int main() {
                std::unique_ptr<D[]> d(new D[5]);
      }
      
      • 这里的类型是D[],而不是D.即数组和单个元素的差别.

      • 调用也有一些差别.

      • 重载的函数也是数组的操作[],不支持*,->操作.

      • 通常用在C风格的函数返回数组.C++一般是使用容器.

    • 转换为shared_ptr

      • 因为是独占的,所以可以理解为共享,只是计数只能为1.

      • 同时支持独占和共享的转换也是作为工厂函数的原因之一.