智能指针【C++11】
智能指针
std::auto_ptr
管理权转移
auto_ptr是C++98中引入的智能指针,auto_ptr通过管理权转移的方式解决智能指针的拷贝问题,保证一个资源在任何时刻都只有一个对象在对其进行管理,这时同一个资源就不会被多次释放了4
class A
{
public:// 构造函数,初始化列表中给成员变量_a赋值A(int a = 0) : _a(a) {std::cout << "A(int a = 0)" << std::endl;}// 析构函数~A() {std::cout << "~A()" << std::endl;}
private:int _a;
};int main()
{auto_ptr<A> ap1(new A(1));auto_ptr<A> ap2(new A(2));//管理权转移,拷贝时,会把被拷贝对象的资源管理权转移给拷贝对象,导致被拷贝对象悬空auto_ptr<A> ap3(ap1);return 0;
}
int main()
{
//std::auto_ptr<int> ap1(new int(1));//管理权转移,拷贝时,会把被拷贝对象的资源管理权转移给拷贝对象,导致被拷贝对象悬空std::auto_ptr<int> ap2(ap1);*ap2 = 10;//*ap1 = 20; //errorstd::auto_ptr<int> ap3(new int(1));std::auto_ptr<int> ap4(new int(2));ap3 = ap4;return 0;
}
一个对象的管理权转移后也就意味着,该对象不能再用对原来管理的资源进行访问了,否则程序就会崩溃,因此使用auto_ptr之前必须先了解它的机制,否则程序很容易出问题,很多公司也都明确规定了禁止使用auto_ptr
简易版的auto_ptr的实现
1、在构造函数中获取资源,在析构函数中释放资源,利用对象的生命周期来控制资源。
2、对*和->运算符进行重载,使auto_ptr对象具有指针一样的行为。
3、在拷贝构造函数中,用传入对象管理的资源来构造当前对象,并将传入对象管理资源的指针置空。
4、在拷贝赋值函数中,先将当前对象管理的资源释放,然后再接管传入对象管理的资源,最后将传入对象管理资源的指针置空
namespace cxq
{template<class T>class auto_ptr{public://RAIIauto_ptr(T* ptr = nullptr):_ptr(ptr){}~auto_ptr(){if (_ptr != nullptr){cout << "delete: " << _ptr << endl;delete _ptr;_ptr = nullptr;}}auto_ptr(auto_ptr<T>& ap):_ptr(ap._ptr){ap._ptr = nullptr; //管理权转移后ap被置空}auto_ptr& operator=(auto_ptr<T>& ap){if (this != &ap){delete _ptr; //释放自己管理的资源_ptr = ap._ptr; //接管ap对象的资源ap._ptr = nullptr; //管理权转移后ap被置空}return *this;}//可以像指针一样使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr; //管理的资源};
}
std::unique_ptr
unique_ptr是C++11中引入的智能指针,unique_ptr通过防拷贝的方式解决智能指针的拷贝问题,也就是简单粗暴的防止对智能指针对象进行拷贝,这样也能保证资源不会被多次释放
int main()
{std::unique_ptr<int> up1(new int(0));//std::unique_ptr<int> up2(up1); //errorreturn 0;
}
简易版的unique_ptr的实现步骤如下:
1、在构造函数中获取资源,在析构函数中释放资源,利用对象的生命周期来控制资源。
2、对*和->运算符进行重载,使unique_ptr对象具有指针一样的行为。
3、用C++98的方式将拷贝构造函数和拷贝赋值函数声明为私有,或者用C++11的方式在这两个函数后面加上=delete,防止外部调用
namespace cxq
{template<class T>class unique_ptr{public://RAIIunique_ptr(T* ptr = nullptr):_ptr(ptr){}~unique_ptr(){if (_ptr != nullptr){cout << "delete: " << _ptr << endl;delete _ptr;_ptr = nullptr;}}//可以像指针一样使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}//防拷贝unique_ptr(unique_ptr<T>& up) = delete;unique_ptr& operator=(unique_ptr<T>& up) = delete;private:T* _ptr; //管理的资源};
}
std::shared_ptr
shared_ptr还会提供一个get函数,用于获取其管理的资源
class Resource
{
public:void display() const {std::cout << "Resource is being used." << std::endl;}
};void useResource(Resource* res)
{if (res){res->display();}
}int main()
{std::shared_ptr<Resource> sp(new Resource());// 使用 get 函数获取原始指针Resource* rawPtr = sp.get();// 使用原始指针调用函数useResource(rawPtr);// 直接使用 shared_ptr 调用成员函数sp->display();return 0;
}
shared_ptr是C++11中引入的智能指针,shared_ptr通过引用计数的方式解决智能指针的拷贝问题。
- 每一个被管理的资源都有一个对应的引用计数,通过这个引用计数记录着当前有多少个对象在管理着这块资源。
- 当新增一个对象管理这块资源时则将该资源对应的引用计数进行++,当一个对象不再管理这块资源或该对象被析构时则将该资源对应的引用计数进行–。
- 当一个资源的引用计数减为0时说明已经没有对象在管理这块资源了,这时就可以将该资源进行释放了
通过这种引用计数的方式就能支持多个对象一起管理某一个资源,也就是支持了智能指针的拷贝,并且只有当一个资源对应的引用计数减为0时才会释放资源,因此保证了同一个资源不会被释放多次
int main()
{// C++11std::shared_ptr<A> sp1(new A(1));std::shared_ptr<A> sp2(new A(2));std::shared_ptr<A> sp3(sp1); // sp3 和 sp1 共享同一个A对象sp1->_a++;sp3->_a++; std::cout << sp1->_a<< std::endl;return 0;
}
namespace cxq
{template<class T>class shared_ptr{public://RAIIshared_ptr(T* ptr = nullptr):_ptr(ptr), _pcount(new int(1)){}~shared_ptr(){if (--(*_pcount) == 0){if (_ptr != nullptr){cout << "delete: " << _ptr << endl;delete _ptr;_ptr = nullptr;}delete _pcount;_pcount = nullptr;}}shared_ptr(shared_ptr<T>& sp):_ptr(sp._ptr), _pcount(sp._pcount){(*_pcount)++;}shared_ptr& operator=(shared_ptr<T>& sp){if (_ptr != sp._ptr) //管理同一块空间的对象之间无需进行赋值操作{if (--(*_pcount) == 0) //将管理的资源对应的引用计数--{cout << "delete: " << _ptr << endl;delete _ptr;delete _pcount;}_ptr = sp._ptr; //与sp对象一同管理它的资源_pcount = sp._pcount; //获取sp对象管理的资源对应的引用计数(*_pcount)++; //新增一个对象来管理该资源,引用计数++}return *this;}//获取引用计数int use_count(){return *_pcount;}//可以像指针一样使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr; //管理的资源int* _pcount; //管理的资源对应的引用计数 ,一个资源对应一个count ,有几个资源就有几个count};
}
int main()
{cxq::shared_ptr<int> sp1(new int(1));cxq::shared_ptr<int> sp2(sp1);*sp1 = 10;*sp2 = 20;cout << sp1.use_count() << endl; //2cxq::shared_ptr<int> sp3(new int(1));cxq::shared_ptr<int> sp4(new int(2));sp3 = sp4;cout << sp3.use_count() << endl; //2return 0;
}
如何理解引用计数需要存放在堆区
shared_ptr中的引用计数count不能单纯的定义成一个int类型的成员变量,因为这就意味着每个shared_ptr对象都有一个自己的count成员变量,而当多个对象要管理同一个资源时,这几个对象应该用到的是同一个引用计数
shared_ptr中的引用计数count也不能定义成一个静态的成员变量,因为静态成员变量是所有类型对象共享的,这会导致管理相同资源的对象和管理不同资源的对象用到的都是同一个引用计数
如果将shared_ptr中的引用计数count定义成一个指针,当一个资源第一次被管理时就在堆区开辟一块空间用于存储其对应的引用计数,如果有其他对象也想要管理这个资源,那么除了将这个资源给它之外,还需要把这个引用计数也给它。
这时管理同一个资源的多个对象访问到的就是同一个引用计数,而管理不同资源的对象访问到的就是不同的引用计数了,相当于将各个资源与其对应的引用计数进行了绑定
std::shared_ptr的线程安全问题
模拟实现的shared_ptr还存在线程安全的问题,由于管理同一个资源的多个对象的引用计数是共享的,因此多个线程可能会同时对同一个引用计数进行自增或自减操作,而自增和自减操作都不是原子操作,因此需要通过加锁来对引用计数进行保护,否则就会导致线程安全问题。
比如下面代码中用一个shared_ptr管理一个整型变量,然后用两个线程分别对这个shared_ptr对象进行1000次拷贝操作,这些对象被拷贝出来后又会立即被销毁
void func(cxq::shared_ptr<int>& sp, size_t n)
{for (size_t i = 0; i < n; i++){cxq::shared_ptr<int> copy(sp);}
}
int main()
{ std::shared_ptr<int> p(new int(0));const size_t n = 1000;thread t1(func, p, n);thread t2(func, p, n);//线程等待t1.join();t2.join();cout << p.use_count() << endl; //预期:1return 0;
}
在这个过程中两个线程会不断对引用计数进行自增和自减操作,理论上最终两个线程执行完毕后引用计数的值应该是1,因为拷贝出来的对象都被销毁了,只剩下最初的shared_ptr对象还在管理这个整型变量,但每次运行程序得到引用计数的值可能都是不一样的,根本原因就是因为对引用计数的自增和自减不是原子操作
解决引用计数的线程安全问题,本质就是要让对引用计数的自增和自减操作变成一个原子操作,因此可以对引用计数的操作进行加锁保护
- 在shared_ptr类中新增互斥锁成员变量,为了让管理同一个资源的多个线程访问到的是同一个互斥锁,管理不同资源的线程访问到的是不同的互斥锁,因此互斥锁也需要在堆区创建。
- 在调用拷贝构造函数和拷贝赋值函数时,除了需要将对应的资源和引用计数交给当前对象管理之外,还需要将对应的互斥锁也交给当前对象。
- 当一个资源对应的引用计数减为0时,除了需要将对应的资源和引用计数进行释放,由于互斥锁也是在堆区创建的,因此还需要将对应的互斥锁进行释放。
- 为了简化代码逻辑,可以将拷贝构造函数和拷贝赋值函数中引用计数的自增操作提取出来,封装成AddRef函数,将拷贝赋值函数和析构函数中引用计数的自减操作提取出来,封装成ReleaseRef函数,这样就只需要对AddRef和ReleaseRef函数进行加锁保护即可
namespace cxq
{template<class T>class shared_ptr{private://++引用计数void AddRef(){_pmutex->lock();(*_pcount)++;_pmutex->unlock();}//--引用计数void ReleaseRef(){_pmutex->lock();bool flag = false;if (--(*_pcount) == 0) //将管理的资源对应的引用计数--{if (_ptr != nullptr){cout << "delete: " << _ptr << endl;delete _ptr;_ptr = nullptr;}delete _pcount;_pcount = nullptr;flag = true;}_pmutex->unlock();if (flag == true){delete _pmutex;}}public://RAIIshared_ptr(T* ptr = nullptr):_ptr(ptr), _pcount(new int(1)), _pmutex(new mutex){}~shared_ptr(){ReleaseRef();}shared_ptr(shared_ptr<T>& sp):_ptr(sp._ptr), _pcount(sp._pcount), _pmutex(sp._pmutex){AddRef();}shared_ptr& operator=(shared_ptr<T>& sp){if (_ptr != sp._ptr) //管理同一块空间的对象之间无需进行赋值操作{ReleaseRef(); //将管理的资源对应的引用计数--_ptr = sp._ptr; //与sp对象一同管理它的资源_pcount = sp._pcount; //获取sp对象管理的资源对应的引用计数_pmutex = sp._pmutex; //获取sp对象管理的资源对应的互斥锁AddRef(); //新增一个对象来管理该资源,引用计数++}return *this;}//获取引用计数int use_count(){return *_pcount;}//可以像指针一样使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr; //管理的资源int* _pcount; //管理的资源对应的引用计数mutex* _pmutex; //管理的资源对应的互斥锁};
}
在ReleaseRef函数中,当引用计数被减为0时需要释放互斥锁资源,但不能在临界区中释放互斥锁,因为后面还需要进行解锁操作,因此代码中借助了一个flag变量,通过flag变量来判断解锁后释放需要释放互斥锁资源。
shared_ptr只需要保证引用计数的线程安全问题,而不需要保证管理的资源的线程安全问题,就像原生指针管理一块内存空间一样,原生指针只需要指向这块空间,而这块空间的线程安全问题应该由这块空间的操作者来保证
std::weak_ptr
解决循环引用问题
weak_ptr是C++11中引入的智能指针,weak_ptr不是用来管理资源的释放的,它主要是用来解决shared_ptr的循环引用问题的
weak_ptr支持用shared_ptr对象来构造weak_ptr对象,构造出来的weak_ptr对象与shared_ptr对象管理同一个资源,但不会增加这块资源对应的引用计数
weak_ptr的模拟实现
1、提供一个无参的构造函数,比如刚才new ListNode时就会调用weak_ptr的无参的构造函数。
2、支持用shared_ptr对象拷贝构造weak_ptr对象,构造时获取shared_ptr对象管理的资源。
3、支持用shared_ptr对象拷贝赋值给weak_ptr对象,赋值时获取shared_ptr对象管理的资源。
4、对*和->运算符进行重载,使weak_ptr对象具有指针一样的行为
namespace cxq
{template<class T>class weak_ptr{public:weak_ptr():_ptr(nullptr){}weak_ptr(const shared_ptr<T>& sp):_ptr(sp.get()){}weak_ptr& operator=(const shared_ptr<T>& sp){_ptr = sp.get();return *this;}//可以像指针一样使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr; //管理的资源};
}
相关文章:

智能指针【C++11】
文章目录 智能指针std::auto_ptr std::unique_ptrstd::shared_ptrstd::shared_ptr的线程安全问题std::weak_ptr 智能指针 std::auto_ptr 管理权转移 auto_ptr是C98中引入的智能指针,auto_ptr通过管理权转移的方式解决智能指针的拷贝问题,保证一个资源…...

【Linux 篇】Docker 启动和停止的精准掌舵:操控指南
文章目录 【Linux篇】Docker 启动和停止的精准掌舵:操控指南前言docker基本命令1. 帮助手册 2. 指令介绍 常用命令1. 查看镜像2. 搜索镜像3. 拉取镜像4. 删除镜像5. 从Docker Hub拉取 容器的相关命令1. 查看容器2. 创建与启动容器3. 查看镜像4. 启动容器5. 查看容器…...

Cursor vs VSCode:主要区别与优势分析
Cursor - The AI Code Editor 1. AI 集成能力 Cursor的优势 原生AI集成: # Cursor可以直接通过快捷键调用AI # 例如:按下 Ctrl K 可以直接获取代码建议 def complex_function():# 在这里,你可以直接询问AI如何实现功能# AI会直接在编辑器中…...

从单体到微服务:如何借助 Spring Cloud 实现架构转型
一、Spring Cloud简介 Spring Cloud 是一套基于 Spring 框架的微服务架构解决方案,它提供了一系列的工具和组件,帮助开发者快速构建分布式系统,尤其是微服务架构。 Spring Cloud 提供了诸如服务发现、配置管理、负载均衡、断路器、消息总线…...

RocketMq基础学习+SpringBoot集成
学习贴:参考https://blog.csdn.net/zhiyikeji/article/details/138286088 文章目录 普通消息顺序消息延迟消息批量消息事务消息 SpringBoot整合RocketMQ 3.1 NameServer NameServer是一个简单的路由注册中心,支持Topic和Broker的动态注册和发现。作用主…...

分布式cap
P(分区安全)都能保证,就是在C(强一致)和A(性能)之间做取舍。 (即立马做主从同步,还是先返回写入结果等会再做主从同步。类似的还有,缓存和db之间的同步。&am…...

mybatis-xml映射文件及mybatis动态sql
规范 XML映射文件的名称与Mapper接口名称一致,并且将XML映射文件和Mapper接口放置在相同包下(同包同名)。 XML映射文件的namespace属性为Mapper接口全限定名一致。 XML映射文件中sql语句的id与Mapper接口中的方法名一致,并保持返回类型一致…...

计算机网络复习——概念强化作业
物理层负责网络通信的二进制传输 用于将MAC地址解析为IP地址的协议为RARP。 一个交换机接收到一帧,其目的地址在它的MAC地址表中查不到,交换机应该向除了来的端口外的所有其它端口转发。 关于ICMP协议,下面的论述中正确的是ICMP可传送IP通信过程中出现的错误信息。 在B类网络…...

用友BIP与旺店通数据集成方案解析
用友BIP与旺店通企业奇门的供应商集成同步方案 在现代企业的数据管理中,跨平台的数据集成是实现高效业务运作的关键环节。本文将分享一个实际案例:如何通过轻易云数据集成平台,将用友BIP系统中的供应商数据无缝对接到旺店通企业奇门…...

string类函数的手动实现
在上一篇文章中,我们讲解了一些string类的函数,但是对于我们要熟练掌握c是远远不够的,今天,我将手动实现一下这些函数~ 注意:本篇文章中会大量应用复用,这是一种很巧妙的方法 和以往一样,还是…...

Oceanbase离线集群部署
准备工作 两台服务器 服务器的配置参照官网要求来 服务器名配置服务器IPoceanbase116g8h192.168.10.239oceanbase216g8h192.168.10.239 这里选oceanbase1作为 obd机器 oceanbase安装包 选择社区版本的时候自己系统的安装包 ntp时间同步rpm包 联网机器下载所需的软件包 …...

transformers生成式对话机器人
简介 生成式对话机器人是一种先进的人工智能系统,它能够通过学习大量的自然语言数据来模拟人类进行开放、连贯且创造性的对话。与基于规则或检索式的聊天机器人不同,生成式对话机器人并不局限于预定义的回答集,而是可以根据对话上下文动态地…...

WPF中的VisualState(视觉状态)
以前在设置控件样式或自定义控件时,都是使用触发器来进行样式更改。触发器可以在属性值发生更改时启动操作。 像这样: <Style TargetType"ListBoxItem"><Setter Property"Opacity" Value"0.5" /><Setter …...

C#设计模式--状态模式(State Pattern)
状态模式是一种行为设计模式,它允许对象在其内部状态发生变化时改变其行为。这种模式的核心思想是将状态封装在独立的对象中,而不是将状态逻辑散布在整个程序中。 用途 简化复杂的条件逻辑:通过将不同的状态封装在不同的类中,可…...

〔 MySQL 〕索引
目录 1. 没有索引,可能会有什么问题 2. 认识磁盘 MySQL与存储 先来研究一下磁盘: 在看看磁盘中一个盘片编辑 扇区 定位扇区编辑 结论 磁盘随机访问(Random Access)与连续访问(Sequential Access) 3. MySQL 与磁盘交互基本单位 4. 建立共识…...

计算机网络研究实训室建设方案
一、概述 本方案旨在规划并实施一个先进的计算机网络研究实训室,旨在为学生提供一个深入学习、实践和研究网络技术的平台。实训室将集教学、实验、研究于一体,覆盖网络基础、网络架构、网络安全、网络管理等多个领域,以培养具备扎实理论基础…...

韩企研学团造访图为科技:共探人工智能创新前沿
今日,一支由韩国知名企业研学专家组成的代表团莅临图为科技深圳总部,展开了一场深度技术交流与研讨活动。 此次访问旨在通过实地探访中国领先的科技企业,促进中韩两国在科技创新领域的深入合作与交流。 韩国游学团合影 图为科技作为一家在人…...

html button 按钮单选且 高亮
<DIV class"middle"> <div class"containerTarget"> <span class"hover-target1" οnclick"btn(1);">韵达 </span> <span class"hover-target2" οnclick"btn(2);">中通 </span…...

图片上传HTML
alioss sky:jwt:# 设置jwt签名加密时使用的秘钥admin-secret-key: itcast# 设置jwt过期时间admin-ttl: 7200000# 设置前端传递过来的令牌名称admin-token-name: tokenalioss:endpoint: ${sky.alioss.endpoint}access-key-id: ${sky.alioss.access-key-id}access-key-secret: $…...

C++学习-函数
C 函数 目录 函数默认参数引用传参函数重载 数量不同类型不同 内联函数 函数默认参数 #include<iostream>using std::cout; using std::endl;int power(int n, int x2); // x2 是默认参数int main() {cout << power(5) << endl; // 没有传 x 的值&#x…...

spring boot 测试 mybatis mapper类
spring boot 测试 mybatis mapper类 针对 mybatis plus不启动 webserver指定加载 xml 【过滤 “classpath*:/mapper/**/*.xml” 下的xml】, mapper xml文件名和mapper java文件名称要一样,是根据文件名称过滤的。默认情况加载和解析所有mapper.xml 自定义 MapperT…...

远程游戏新体验!
在这个数字化的时代,游戏已经不仅限于家里的电视或书房的电脑了。远程游戏,也就是通过远程控制软件在不同地点操作游戏设备,给玩家带来了前所未有的自由和灵活性。RayLink远程控制软件,凭借其出色的性能和专为游戏设计的功能&…...

Let up bring up a linux.part2 [十一]
之前的篇幅中我们已经将 Linux 内核 bringup 起来了,不知道大家有没有去尝试将根文件系统运行起来,今天我就带领大家完成这个事情,可以跟着下面的步骤一步步来完成: 在这里我们使用 busybox 构建 rootfs: 下载 busyb…...

调用大模型api 批量处理图像 保存到excel
最近需要调用大模型,并将结果保存到excel中,效果如下: 代码: import base64 from zhipuai import ZhipuAI import os import pandas as pd from openpyxl import Workbook from openpyxl.drawing.image import Image from io i…...

使用 Flownex 模拟热环境对原油运输的影响
石油和天然气行业经常使用管道仿真来模拟原油的流动。为了准确估计管道容量,必须考虑环境对管道的热影响以及环境温度如何影响油品特性。本博客介绍了如何通过将传热元件集成到管道流网中,以及使用新的工作液材料 Flownex 来模拟各种传热机制。 使用 Fl…...

【WRF-Urban】WPS中有关Urban的变量设置
【WRF-Urban】WPS中有关Urban的变量设置 地理数据源的配置WRF-Urban所需静态地理数据1、LANDUSE:包含城市地表分类的土地利用数据。2、URB_PARAM:城市参数数据集。3、FRC_URB2D:城市覆盖度数据集WRF默认设置(美国)数据集1-National urban dataset in China NUDC(中国)数…...

Socket编程-tcp
1. 前言 在tcp套接字编程这里,我们将完成两份代码,一份是基于tcp实现普通的对话,另一份加上业务,client输入要执行的命令,server将执行结果返回给client 2. tcp_echo_server 与udp类似,前两步࿱…...

Redis 之持久化
目录 介绍 RDB RDB生成方式 自动触发 手动触发 AOF(append-only file) Redis 4.0 混合持久化 Redis主从工作原理 总结 介绍 Redis提供了两个持久化数据的能力,RDB Snapshot 和 AOF(Append Only FIle)…...

视频监控汇聚平台:Liveweb安防监控平台实现接入监控视频集中管理方案
随着各行业数字化转型的不断推进,视频监控技术在行业内的安防应用及管理支撑日益增多。然而,由于前期规划不清晰、管理不到位等问题,视频监管系统普遍存在以下问题: 1. 各部门单位在视频平台建设中以所属领域为单位,导…...

ABAP - 系统集成之SAP的数据同步到OA(泛微E9)服务器数据库
需求背景 项目经理说每次OA下单都需要调用一次SAP的接口获取数据,导致效率太慢了,能否把SAP的数据保存到OA的数据库表里,这样OA可以直接从数据库表里获取数据效率快很多。思来想去,提供了两个方案。 在集群SAP节点下增加一个SQL S…...