详解C++ 智能指针的删除器

 更新时间:2025年05月20日 08:47:40   作者:saltymilk  
标准库为智能指针提供了两个默认版本的删除器,可简化智能指针的代码编写,这篇文章主要介绍了C++智能指针的删除器的相关知识,需要的朋友可以参考下

为什么要设置删除器

C++11 加入STL的 shared_ptr 和 unique_ptr,已经是我们编码的常客了。用的多自然就会了解到它们的删除器,比如很多C语言库(GDAL, GLFW, libcurl等等)创建的指针不能简单的使用 delete 释放,当我们想使用智能指针管理这些库创建的资源时,必须设置删除器:

//使用重载了operator()的类作为删除器
struct CurlCleaner
{
  void operator()(CURL *ptr) const
  {
    curl_easy_cleanup(ptr);
  }
};
std::unique_ptr<CURL, CurlCleaner> curlu(curl_easy_init(), CurlCleaner{});//第二个参数可省略,因为CurlCleaner可默认构造
std::shared_ptr<CURL> curls(curl_easy_init(), CurlCleaner{});
//使用函数指针作为删除器
void GLFWClean(GLFWwindow *wnd)
{
  glfwDestroyWindow(wnd);
}
std::unique_ptr<GLFWwindow, decltype(&GLFWClean)> glfwu(glfwCreateWindow(/*省略*/), GLFWClean);//第二个参数必须传入实际调用的函数地址
std::shared_ptr<GLFWwindow> glfws(glfwCreateWindow(/*省略*/), GLFWClean);
//上述两个构造函数中的第二个参数都进行了函数名到函数指针的隐式转换
//使用lambda作为删除器
auto GDALClean=[](GDALDataset *dataset){ GDALClose(dataset); };
std::unique_ptr<GDALDataset, decltype(GDALClean)> gdalu(GDALOpen(/*省略*/), GDALClean);//lambda无法默认构造,必须传入一个实例
std::shared_ptr<GDALDataset> gdals(GDALOpen(/*省略*/), GDALClean);

上面是三种最常使用的自定义删除器形式,也可以利用 std::function 的强大适配能力来包装可调用对象作为删除器,此处不展开。

标准库提供的默认删除器

内置类型和析构函数为 public 的类类型,无需指定删除器,智能指针会在引用计数归零时自动调用 delete 对管理的指针进行释放,使得语法相对简洁:

std::unique_ptr<int> pi(new int(42));
std::shared_ptr<float> pf(new float(0.0f));
std::unique_ptr<std::vector<int>> pveci(new std::vector<int>());
std::unique_ptr<std::list<int>> plsti(new std::list<int>());

很长一段时间内,我以为智能指针只有 delete 一个默认的删除器,所以每次在管理 new[] 得到的指针时,都会为它编写调用 delete[] 的删除器,直到翻看智能指针的源码,发现它们的默认删除器其实有一个针对数组形式指针的特化版本:

template<class _Tp>
struct default_delete//默认删除器主模板
{
  ...
  void operator()(_Tp *_Ptr) const noexcept
  {
    ...
    delete _Ptr;//使用delete释放指针
  }
  ...
}
template<class _Tp>
struct default_delete<_Tp[]>//针对数组形式的特化版本
{
  ...
  void operator()(_Tp *_Ptr) const noexcept
  {
    ...
    delete[] _Ptr;//使用delete[]释放指针
  }
  ...
}
//unique_ptr
template<class _Tp, class _Dp = default_delete<_Tp>/*默认删除器*/>
class unique_ptr{...};
//shared_ptr
template <class, class _Yp>//辅助类主模板,普通指针应用该版本
struct __shared_ptr_default_delete : default_delete<_Yp> {};
template <class _Yp, class _Un, size_t _Sz>//数组形式特化,匹配固定长度的数组形式,如std::shared_ptr<int[10]>
struct __shared_ptr_default_delete<_Yp[_Sz], _Un> : default_delete<_Yp[]> {};
template <class _Yp, class _Un>//数组形式特化,匹配不定长度的数组形式,如std::shared_ptr<int[]>
struct __shared_ptr_default_delete<_Yp[], _Un> : default_delete<_Yp[]> {};
template<class _Tp>
class shared_ptr
{
  ...
  template <class _Yp,/*检查_Yp指针是否可转换为_Tp指针(比如子类指针到基类指针)、_Yp类型是否可应用delete与delete[]操作*/>
  explicit shared_ptr(_Yp* __p) : __ptr_(__p) {
    ...
    typedef __shared_ptr_pointer<_Yp*, __shared_ptr_default_delete<_Tp, _Yp>/*根据_Yp类型选择合适的默认删除器*/, _AllocT> _CntrlBlk;
    __cntrl_ = new _CntrlBlk(__p, __shared_ptr_default_delete<_Tp, _Yp>(), _AllocT());
    ...
  }
  ...
};
//用户代码
std::unique_ptr<int[]> piu(new int[10]);//匹配int[]版本,删除器编译为使用delete[]释放指针
std::shared_ptr<int[]> pis(new int[10]);//构造函数内选择使用delete[]释放指针的删除器

以上代码节选自 llvm-mingw 的标准库,查看了一下手头上的几个版本的标准库实现,发现 unique_ptr 的实现大致类似。值得一提的是,unique_ptr 自身也有针对数组形式的特化版本 unique_ptr<_Tp[]>,由于知晓管理的是数组形式的指针,这个特化版本不提供 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, 该类有一系列重载的构造函数,其中几个是:

struct __sp_array_delete
{
  template<typename _Yp>
  void operator()(_Yp *__p) const 
  {
    delete[] __p;
  }
};
r1: template<typename _Ptr>/*默认使用delete版本的删除器,省略实现*/
    explicit __shared_count(_Ptr __p)
r2: template<typename _Ptr>/*委托给r1*/
    __shared_count(_Ptr __P, false_type) : __shared_count(__p){}
r3: template<typename _Ptr, typename _Deleter, typename _Alloc, typename = typename __not_alloc_shared_tag<_Deleter>::type>
    __shared_count(_Ptr __p, _Deleter __d, _Alloc __a)/*可指定删除器、内存分配器的版本*/
r4: template<typename _Ptr>/*委托给r3*/
    __shared_count(_Ptr __p, true_type) : __shared_count(__p, __sp_array_delete{}, allocator<void>()){}
//__shared_ptr的接受一个指针参数的构造函数
template<typename _Yp, /*检查_Yp *是否和转换为类的实例化指针类型*/>
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.标准库为智能指针提供了两个默认版本的删除器,可简化智能指针的代码编写

到此这篇关于C++ 智能指针的删除器的文章就介绍到这了,更多相关C++ 智能指针内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C语言实现个人财务管理软件

    C语言实现个人财务管理软件

    这篇文章主要为大家详细介绍了C语言实现个人财务管理软件,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-05-05
  • VC基于ADO技术访问数据库的方法

    VC基于ADO技术访问数据库的方法

    这篇文章主要介绍了VC基于ADO技术访问数据库的方法,较为详细的分析了VC使用ADO操作数据库的相关实现技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-10-10
  • C++ 程序员为什么看不起php程序员

    C++ 程序员为什么看不起php程序员

    由于当今市场状况,各种培训班飞起,PHPer越来越多,学习成本很低。导致了很多人对PHP的误解。其实PHP学到深入的时候,所需知识很多,并不是表面看到的那样。另外,PHP确实严谨性不高,这个跟C++,java确实都没法比。但是,PHP在web开发中的效率,是其他语言所不能比的
    2017-02-02
  • c语言判断是否素数程序代码

    c语言判断是否素数程序代码

    这篇文章主要介绍了c语言判断是否素数的方法和问题,大家参考使用吧
    2013-11-11
  • C++超详细讲解友元的使用

    C++超详细讲解友元的使用

    采用类的机制后实现了数据的隐藏与封装,类的数据成员一般定义为私有成员,成员函数一般定义为公有的,依此提供类与外界间的通信接口。但是,有时需要定义一些函数,这些函数不是类的一部分,但又需要频繁地访问类的数据成员,这时可以将这些函数定义为该类的友元函数
    2022-04-04
  • Qt Creator使用教程的简单说明

    Qt Creator使用教程的简单说明

    如今 Qt Creator 功能十分强大了,包含项目模板生成、代码编辑、UI 设计、QML 界面编辑、调试程序、上下文帮助等丰富功能,本文就详细的介绍一下如何使用
    2021-08-08
  • C语言单值二叉树真题讲解

    C语言单值二叉树真题讲解

    单值二叉树你可能之前没见过,如果二叉树每个节点都具有相同的值,那么该二叉树就是单值二叉树,让我们通过一个真题来深刻了解它吧
    2022-04-04
  • C语言编程中建立和解除内存映射的方法

    C语言编程中建立和解除内存映射的方法

    这篇文章主要介绍了C语言编程中建立和解除内存映射的方法,分别为mmap()函数和munmap()函数的使用,需要的朋友可以参考下
    2015-08-08
  • C++使用ffmpeg实现rtsp取流的代码

    C++使用ffmpeg实现rtsp取流的代码

    这篇文章主要介绍了C++使用ffmpeg实现rtsp取流,文章介绍了ffmepg采用rtsp取流流程图,CMakeLists.txt编写方法,通过示例代码给大家介绍的非常详细,需要的朋友可以参考下
    2022-04-04
  • C语言实现简单扫雷小程序

    C语言实现简单扫雷小程序

    这篇文章主要为大家详细介绍了C语言实现简单扫雷小程序,一款大众类的益智小游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-10-10

最新评论