C++ 智能指针的删除器

[复制链接]
发表于 2025-5-19 17:05:45 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
为什么要设置删除器

C++11 加入STL的 shared_ptr 和 unique_ptr,已经是我们编码的常客了。用的多天然就会相识到它们的删除器,比如许多C语言库(GDAL, GLFW, libcurl等等)创建的指针不能简朴的使用 delete 释放,当我们想使用智能指针管理这些库创建的资源时,必须设置删除器:
代码
  1. //使用重载了operator()的类作为删除器
  2. struct CurlCleaner
  3. {
  4.   void operator()(CURL *ptr) const
  5.   {
  6.     curl_easy_cleanup(ptr);
  7.   }
  8. };
  9. std::unique_ptr<CURL, CurlCleaner> curlu(curl_easy_init(), CurlCleaner{});//第二个参数可省略,因为CurlCleaner可默认构造
  10. std::shared_ptr<CURL> curls(curl_easy_init(), CurlCleaner{});
  11. //使用函数指针作为删除器
  12. void GLFWClean(GLFWwindow *wnd)
  13. {
  14.   glfwDestroyWindow(wnd);
  15. }
  16. std::unique_ptr<GLFWwindow, decltype(&GLFWClean)> glfwu(glfwCreateWindow(/*省略*/), GLFWClean);//第二个参数必须传入实际调用的函数地址
  17. std::shared_ptr<GLFWwindow> glfws(glfwCreateWindow(/*省略*/), GLFWClean);
  18. //上述两个构造函数中的第二个参数都进行了函数名到函数指针的隐式转换
  19. //使用lambda作为删除器
  20. auto GDALClean=[](GDALDataset *dataset){ GDALClose(dataset); };
  21. std::unique_ptr<GDALDataset, decltype(GDALClean)> gdalu(GDALOpen(/*省略*/), GDALClean);//lambda无法默认构造,必须传入一个实例
  22. std::shared_ptr<GDALDataset> gdals(GDALOpen(/*省略*/), GDALClean);
复制代码
上面是三种最常使用的自定义删除器形式,也可以利用 std::function 的强盛适配能力来包装可调用对象作为删除器,此处不展开。
标准库提供的默认删除器

内置范例和析构函数为 public 的类范例,无需指定删除器,智能指针会在引用计数归零时自动调用 delete 对管理的指针进行释放,使得语法相对简洁:
代码
  1. std::unique_ptr<int> pi(new int(42));
  2. std::shared_ptr<float> pf(new float(0.0f));
  3. std::unique_ptr<std::vector<int>> pveci(new std::vector<int>());
  4. std::unique_ptr<std::list<int>> plsti(new std::list<int>());
复制代码
很长一段时间内,我以为智能指针只有 delete 一个默认的删除器,以是每次在管理 new[] 得到的指针时,都会为它编写调用 delete[] 的删除器,直到翻看智能指针的源码,发现它们的默认删除器实在有一个针对数组形式指针的特化版本:代码
  1. template<class _Tp>
  2. struct default_delete//默认删除器主模板
  3. {
  4.   ...
  5.   void operator()(_Tp *_Ptr) const noexcept
  6.   {
  7.     ...
  8.     delete _Ptr;//使用delete释放指针
  9.   }
  10.   ...
  11. }
  12. template<class _Tp>
  13. struct default_delete<_Tp[]>//针对数组形式的特化版本
  14. {
  15.   ...
  16.   void operator()(_Tp *_Ptr) const noexcept
  17.   {
  18.     ...
  19.     delete[] _Ptr;//使用delete[]释放指针
  20.   }
  21.   ...
  22. }
  23. //unique_ptr
  24. template<class _Tp, class _Dp = default_delete<_Tp>/*默认删除器*/>
  25. class unique_ptr{...};
  26. //shared_ptr
  27. template <class, class _Yp>//辅助类主模板,普通指针应用该版本
  28. struct __shared_ptr_default_delete : default_delete<_Yp> {};
  29. template <class _Yp, class _Un, size_t _Sz>//数组形式特化,匹配固定长度的数组形式,如std::shared_ptr<int[10]>
  30. struct __shared_ptr_default_delete<_Yp[_Sz], _Un> : default_delete<_Yp[]> {};
  31. template <class _Yp, class _Un>//数组形式特化,匹配不定长度的数组形式,如std::shared_ptr<int[]>
  32. struct __shared_ptr_default_delete<_Yp[], _Un> : default_delete<_Yp[]> {};
  33. template<class _Tp>
  34. class shared_ptr
  35. {
  36.   ...
  37.   template <class _Yp,/*检查_Yp指针是否可转换为_Tp指针(比如子类指针到基类指针)、_Yp类型是否可应用delete与delete[]操作*/>
  38.   explicit shared_ptr(_Yp* __p) : __ptr_(__p) {
  39.     ...
  40.     typedef __shared_ptr_pointer<_Yp*, __shared_ptr_default_delete<_Tp, _Yp>/*根据_Yp类型选择合适的默认删除器*/, _AllocT> _CntrlBlk;
  41.     __cntrl_ = new _CntrlBlk(__p, __shared_ptr_default_delete<_Tp, _Yp>(), _AllocT());
  42.     ...
  43.   }
  44.   ...
  45. };
  46. //用户代码
  47. std::unique_ptr<int[]> piu(new int[10]);//匹配int[]版本,删除器编译为使用delete[]释放指针
  48. std::shared_ptr<int[]> pis(new int[10]);//构造函数内选择使用delete[]释放指针的删除器
复制代码
以上代码节选自 llvm-mingw 的标准库,检察了一下手头上的几个版本的标准库实现,发现 unique_ptr 的实现大抵类似。值得一提的是,unique_ptr 自身也有针对数组形式的特化版本 unique_ptr,由于知晓管理的是数组形式的指针,这个特化版本不提供 operator-> 访问符号,取而代之的是 operator[] 来访问数组数据。

llvm-mingw shared_ptr 默认删除器的选择是通过辅助模板类 __shared_ptr_default_delete 的特化来实现的;MSVC 版本中 shared_ptr 的构造函数则直接使用 if consexpr(虽然是 C++17 开始支持,但是发现 C++14 版本的代码中已经使用)判断实例化指针范例是否为数组形式选择相应删除器;GCC 的 shared_ptr 逻辑相对复杂一些,其 shared_ptr 继承自 __shared_ptr,而 __shared_ptr 有一个范例为 __shared_count 的成员 _M_refcount, 该类有一系列重载的构造函数,其中几个是:代码
  1. struct __sp_array_delete
  2. {
  3.   template<typename _Yp>
  4.   void operator()(_Yp *__p) const
  5.   {
  6.     delete[] __p;
  7.   }
  8. };
  9. r1: template<typename _Ptr>/*默认使用delete版本的删除器,省略实现*/
  10.     explicit __shared_count(_Ptr __p)
  11. r2: template<typename _Ptr>/*委托给r1*/
  12.     __shared_count(_Ptr __P, false_type) : __shared_count(__p){}
  13. r3: template<typename _Ptr, typename _Deleter, typename _Alloc, typename = typename __not_alloc_shared_tag<_Deleter>::type>
  14.     __shared_count(_Ptr __p, _Deleter __d, _Alloc __a)/*可指定删除器、内存分配器的版本*/
  15. r4: template<typename _Ptr>/*委托给r3*/
  16.     __shared_count(_Ptr __p, true_type) : __shared_count(__p, __sp_array_delete{}, allocator<void>()){}
  17. //__shared_ptr的接受一个指针参数的构造函数
  18. template<typename _Yp, /*检查_Yp *是否和转换为类的实例化指针类型*/>
  19. explicit __shared_ptr(_Yp *__p): _M_ptr(__p), _M_refcount(__p, typename is_array<_Tp>::type()){...}
复制代码
通过代码我们大抵可以推测,__shared_count 这个类是用来管理引用计数和删除器的类。
可以看到,假如 __shared_ptr 构造函数担当的指针范例为平凡指针,会调用 __shared_count(__p, false_type) 将 _M_refcount 构造为使用 delete 释放指针的版本;而当它担当的指针范例为数组形式指针时,__shared_count(__p, true_type) 则会被调用,构造的 _M_refcount 存储的删除器是 __sp_array_delete 范例,这个范例使用 delete[] 释放指针。
总结

1.烧毁前需要额外资源释放操作的范例,使用智能指针管理时必须设置自定义删除器
2.标准库为智能指针提供了两个默认版本的删除器,可简化智能指针的代码编写
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表