当前位置: 首页 > news >正文

【C++ 第二十章】模拟实现 shared_ptr(可以拷贝的智能指针)

在这里插入图片描述

本文主要讲解如果简单模拟实现库中的 shared_ptr
而不会过多的对库中的 shared_ptr 讲解



1. 初始版本

智能指针的基本框架

namespace my
{template<class T>class shared_ptr{public:shared_ptr(T* ptr = nullptr):_ptr(ptr){}~shared_ptr() {delete _ptr;_ptr = nullptr;}T& operator*() {return *_ptr;}T* operator->() {return _ptr;}private:T* _ptr;};
}



2. 添加 引用计数器


因为一个资源需要对应一个 计数器

不能设置成:

普通成员:一个对象就有一个,达不到共同管理一个资源的目的

静态成员:所有对象共用一个,但达不到 不同资源对应不同计数器 的目的


解决办法:

(1)可以在创建管理这块资源的第一个对象时,创建新的计数器

(2)同时将 计数器 存储在堆区:即 new int(1),使其不会随一个对象的释放而销毁


引用计数的规则

(1)如果引用计数是 0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
计数=0,则表示自己是最后一个人,可以释放空间了

(2)如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。
计数不等于0,则表示自己不是是最后一个人,没有释放资源的权力




namespace my
{template<class T>class shared_ptr{public:shared_ptr(T* ptr = nullptr):_ptr(ptr): _pCount(new int(1))  // 将 引用计数 开辟在堆里面{}// 每次调用析构都 计数-1, 如果计数=0,说明该对象是最后一个管理该资源的对象,可以直接释放该资源~shared_ptr() {if (--(*_pCount) == 0) {delete _ptr;delete _pCount;_pCount = nullptr;_ptr = nullptr;}}T& operator*() {return *_ptr;}T* operator->() {return _ptr;}private:T* _ptr;int* _pCount;  // 引用计数};
}



3. 添加 赋值重载 + 拷贝构造

实现思路:
拷贝构造:就是将自己的指针指向 传递过来的对象 管理的资源,同时 计数+1
赋值重载:思路差不多

namespace my
{template<class T>class shared_ptr{typedef shared_ptr Self;public:shared_ptr(T* ptr = nullptr):_ptr(ptr), _pCount(new int(1)){}// 拷贝构造:即创建一个新对象指针指向该资源,代表又有一个对象管理该资源,因此计数+1shared_ptr(Self& s) {_ptr = s._ptr;_pCount = s._pCount;(*_pCount)++;}// 赋值重载:Self& operator=(Self& s) {if (_ptr != s._ptr) {  // 防止自己赋值给自己:浪费// 先处理旧关系,再处理新关系Realse();  // 和之前管理的资源断开关系,其实这里就是一次析构的过程,但是不建议直接调用析构函数(可能会引发一些未知错误), 可以将析构函数的逻辑提取出来设计 Realse 函数// 管理新资源_ptr = s._ptr;_pCount = s._pCount;(*_pCount)++;}return *this;}void Realse() {if (--(*_pCount) == 0) {delete _ptr;delete _pCount;_pCount = nullptr;_ptr = nullptr;}}~shared_ptr() {Realse();}T& operator*() {return *_ptr;}T* operator->() {return _ptr;}private:T* _ptr;int* _pCount;};
}

测试代码

int main() {my::shared_ptr<int> mySp1(new int(2024));my::shared_ptr<int> mySp2 = mySp1;my::shared_ptr<int> mySp3;mySp3 = mySp1;cout << *mySp1 << '\n';cout << *mySp2 << '\n';cout << *mySp3 << '\n';
}



上面的代码还存在一个问题:析构函数中 delete _ptr 释放指向的空间资源,按照C++语法,
如果 _ptr = new int(),则 delete _ptr;
如果 _ptr = new int[10],则 delete[] _ptr
如果不对应使用 正确的delete,会导致内存泄漏及其他一些问题
因此析构函数中的 realse 函数就不能固定写死成:delete _ptr
可以使用仿函数解决此问题:定制删除器



4.添加 定制删除器

看C++库中,shared_ptr 的删除器(仿函数),并不是在函数模板参数处传递,而是直接作为 构造函数的参数传递,这意味着它需要多写一个 函数模板的构造函数


在这里插入图片描述

namespace my
{template<class T>class shared_ptr{typedef shared_ptr Self;public:shared_ptr(T* ptr = nullptr):_ptr(ptr), _pCount(new int(1)){}template<class D>shared_ptr(T* ptr, D Delete)  // 重载函数别写 T* ptr = nullptr,默认参数只能写在一个地方,否则这里报错奇奇怪怪: _ptr(ptr), _pCount(new int(1)), _Delete(Delete){}// 拷贝构造:即创建一个新对象指针指向该资源,代表又有一个对象管理该资源,因此计数+1shared_ptr(Self& s) {_ptr = s._ptr;_pCount = s._pCount;(*_pCount)++;}// 赋值重载:Self& operator=(Self& s) {if (_ptr != s._ptr) {  // 防止自己赋值给自己:浪费// 先处理旧关系,再处理新关系Realse();  // 和之前管理的资源断开关系,其实这里就是一次析构的过程,但是不建议直接调用析构函数(可能会引发一些未知错误), 可以将析构函数的逻辑提取出来设计 Realse 函数// 管理新资源_ptr = s._ptr;_pCount = s._pCount;(*_pCount)++;}return *this;}void Realse() {if (--(*_pCount) == 0) {_Delete(_ptr);  // 使用定制删除器// delete _ptr;delete _pCount;_pCount = nullptr;_ptr = nullptr;}}~shared_ptr() {Realse();}T& operator*() {return *_ptr;}T* operator->() {return _ptr;}private:T* _ptr;int* _pCount;function<void(T*)> _Delete = [](const T* ptr) {delete ptr; };  // 默认删除器:对非数组类型直接 delete};
}



测试代码

int main() {// 因为 shared_ptr 内部默认的删除器使用的是 delete,下面这里需要删除int数组,因此需要 delete[],则自己传仿函数对象my::shared_ptr<int> mySp1(new int[10]{ 1, 2, 3 }, [](const int* ptr) {delete[] ptr;cout << "delete[] ptr; " << '\n';});// malloc 需要使用 free 释放,需要自己传定制的删除器 my::shared_ptr<int> mySp2((int*)malloc(40), [](int* ptr) {free(ptr);cout << "free(ptr);" << '\n';});
}




http://www.mrgr.cn/news/19366.html

相关文章:

  • 大屏适配各分辨率屏幕方案及整合动画性能
  • 公式编辑支持SUBSTITUTE()函数
  • 一起学习LeetCode热题100道(70/100)
  • 2024年住宅代理市场概况:趋势与选择指南
  • Robotics: computational motion planning 部分笔记—— week 1 graph-based
  • 数据结构——开篇
  • 【Python】Urllib:发送请求
  • 【STM32+HAL库】---- 高级定时器利用重复计数器输出指定个数PWM
  • 前端按钮通过浏览器下载附件
  • 《Foundation 滑块》
  • Vue:F11全屏模式状态监听,识别
  • 零风险!零付费!我把 AI 接入微信群,爸妈玩嗨了~附教程(下):大模型 API 接入
  • 达梦数据库+JPA+Springboot 报错 :无效的列名
  • 使用 docker 部署 kvm 图形化管理工具 WebVirtMgr
  • 不小心删除丢失了所有短信?如何在 iPhone 上查找和恢复误删除的短信
  • 【重磅推荐】《一本书读懂大模型:技术创新、商业应用与产业变革》发布!大模型零基础入门到精通
  • Webview Android性能优化
  • 花10秒进来学学吧!用AI画朵云,点赞也能10万+
  • 手机怎么把wmv转换成mp4格式?视频格式这样做,让你的视频更加通用
  • 【Qt笔记】QListWidget控件详解