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++ 智能指针的删除器的更多相关文章
- 【STL学习】智能指针之shared_ptr
前面已经学习过auto_ptr,这里补充另外一种智能指针,比auto_ptr要更强力更通用的shared_ptr. shared_ptr 简介及使用选择 几乎所有的程序都需要某种形式的引用计数智能指 ...
- C++ 智能指针浅析
C++ 智能指针浅析 为了解决 C++ 中内存管理这个老大难问题,C++ 11 中提供了三种可用的智能指针.(早期标准库中还存在一种 auto_ptr,但由于设计上的缺陷,已经被 unique_ptr ...
- c++ 智能指针(转)
智能指针的使用 智能指针是在 <memory> 标头文件中的 std 命名空间中定义的. 它们对 RAII 或“获取资源即初始化”编程惯用法至关重要. 此习惯用法的主要目的是确保资源获取与 ...
- C++几个技巧:智能指针在消息传递中的使用,元组,及lambda删除器
1.SendMessage/PostMessage中传递对象参数 (1)方法1:使用shared_ptr 发送端: PostMessage(MyhWnd, CWM_SOME_ERROR, 0, rei ...
- 智能指针shared_ptr的用法
为了解决C++内存泄漏的问题,C++11引入了智能指针(Smart Pointer). 智能指针的原理是,接受一个申请好的内存地址,构造一个保存在栈上的智能指针对象,当程序退出栈的作用域范围后,由于栈 ...
- 关于智能指针boost::shared_ptr
boost库中的智能指针shared_ptr, 功能强大, 且开销小,故受到广大coder的欢迎. 但在实际的使用过程中,笔者也发现了一些不足. 1.定制的删除器 shared_ptr除了可以使用默认 ...
- C++ Primer : 第十二章 : 动态内存之shared_ptr与new的结合使用、智能指针异常
shared_ptr和new结合使用 一个shared_ptr默认初始化为一个空指针.我们也可以使用new返回的指针来初始化一个shared_ptr: shared_ptr<double> ...
- [转]C++智能指针的创建
zero 坐在餐桌前,机械的重复“夹菜 -> 咀嚼 -> 吞咽”的动作序列,脸上用无形的大字写着:我心不在焉.在他的对面坐着 Solmyr ,慢条斯理的吃着他那份午餐,维持着他一贯很有修养 ...
- 智能指针 shared_ptr 解析
近期正在进行<Effective C++>的第二遍阅读,书里面多个条款涉及到了shared_ptr智能指针,介绍的太分散,学习起来麻烦.写篇blog整理一下. LinJM @HQU s ...
- 智能指针剖析(下)boost::shared_ptr&其他
1. boost::shared_ptr 前面我已经讲解了两个比较简单的智能指针,它们都有各自的优缺点.由于 boost::scoped_ptr 独享所有权,当我们真真需要复制智能指针时,需求便满足不 ...
随机推荐
- Docker安装及卸载小白教程(附基本使用命令)
第一种:宝塔面板 安装最新版宝塔面板,左侧docker进去安装,等着完成就好 第二种:命令安装 在终端root用户下,依次输入下方指令安装 1.yum包更新到最新 yum update 2.安装需要的 ...
- Edge、谷歌浏览器默认下载器开启多线程下载
浏览器默认下载器开启多线程下载 Chrome 浏览器,地址栏输入并回车: chrome://flags/#enable-parallel-downloading Edge 新版浏览器,地址栏输入并回车 ...
- 浅谈Processing中的 println() 打印输出函数[String]
简单看一下Processing中的打印输出函数println()相关用法. 部分源码学习 /** * ( begin auto-generated from println.xml ) * * Wri ...
- C# 域套接字通讯类
public class UdsClient { public Socket _socket { get; set; } public UnixDomainSocketEndPoint endPoin ...
- Windows 提权-内核利用_1
本文通过 Google 翻译 Kernel Exploits Part 1 – Windows Privilege Escalation 这篇文章所产生,本人仅是对机器翻译中部分表达别扭的字词进行了校 ...
- linux php安装mongodb 扩展
下载扩展 首先从这个网站选择适合你当前 php 版本的的 mongodb 扩展 https://pecl.php.net/package/mongodb wget https://pecl.php.n ...
- ORACLE SQL中执行先后次序的问题
分享一个经验 需求:Oracle中,根据COST优先级取最优先的一条记录脚本: select ... from ... where ... and rownum=1 order by cost 实际不 ...
- 堆排序(标准版)(NB)
博客地址:https://www.cnblogs.com/zylyehuo/ # _*_coding:utf-8_*_ import random def sift(li, low, high): # ...
- win10/11 禁用移动热点,无法启用
将网络重制即可
- Oracle 11G R2 安装图解
个人学习需要,在Windows Server 2008 R2 上安装 Oracle 11G R2 Tips:需要下载2个文件,file1和file2 解压后需要合并到同一个文件夹下才能正常安装(这里就 ...