智能指针【C++11】

文章目录

  • 智能指针
  • std::auto_ptr
  • std::unique_ptr
  • std::shared_ptr
  • std::shared_ptr的线程安全问题
  • std::weak_ptr

智能指针

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; //管理的资源};
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/35355.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

Win11 24h2 不能正常ensp

Win11 24h2 不能正常ensp 因为Win11 24h2的内核大小更改&#xff0c;目前virtualbox在7.1.4中更新解决了。而ensp不支持5.2.44之后的virtualbox并已停止维护&#xff0c;不再进行5.2.44修复&#xff0c;virtualbox 5.2.24的ntdll文件sizeofimage问题&#xff0c;此问题导致ens…

使用GO--Swagger生成文档

概述 在前后端分离的项目中&#xff0c;后端配置swagger可以很好的帮助前端人员了解后端接口参数和数据传输。go-swagger 是一个功能全面且高性能的Go语言实现工具包&#xff0c;用于处理Swagger 2.0&#xff08;即OpenAPI 2.0&#xff09;规范。它提供了丰富的工具集&#x…

沃德云商协系统微信小程序PHP+Uniapp

“多组织”的云服务平台&#xff0c;打造总商会、总协会、总校友会、工商联等多组织无障碍沟通合作平台&#xff0c;让各大分会、各大分校友会、分组织实现轻松管理&#xff0c;线上宣传展示、商机挖掘、会员管理、会员服务、跨界交流等, 借助沃德云商协平台系统&#xff0c;让…

网站打开速度测试工具:互联网优化的得力助手

在信息飞速流转的互联网时代&#xff0c;网站如同企业与用户对话的窗口&#xff0c;其打开速度直接关乎用户体验&#xff0c;乃至业务的成败。所幸&#xff0c;一系列专业的网站打开速度测试工具应运而生&#xff0c;它们宛如幕后的技术侦探&#xff0c;精准剖析网站性能&#…

shell脚本实战案例

文章目录 实战第一坑功能说明脚本实现 实战第一坑 实战第一坑&#xff1a;在Windows系统写了一个脚本&#xff0c;比如上面&#xff0c;随后上传到服务&#xff0c;执行会报错 原因&#xff1a; 解决方案&#xff1a;在linux系统touch文件&#xff0c;并通过vim添加内容&…

Face2QR:可根据人脸图像生成二维码,还可以扫描,以后个人名片就这样用了!

今天给大家介绍的是一种专为生成个性化二维码而设计的新方法Face2QR&#xff0c;可以将美观、人脸识别和可扫描性完美地融合在一起。 下图展示为Face2QR 生成的面部图像&#xff08;第一行&#xff09;和二维码图像&#xff08;第二行&#xff09;。生成的二维码不仅忠实地保留…

数据结构---队列(Queue)

1. 简介 队列&#xff08;Queue&#xff09;是一种常用的数据结构&#xff0c;它遵循先进先出&#xff08;FIFO&#xff0c;First In First Out&#xff09;的原则。这意味着第一个进入队列的元素将是第一个被移除的元素。队列在计算机科学中有着广泛的应用&#xff0c;比如任…

玩游戏没有flash插件的解决方案(No Flash)

一、概述 在网页游戏开发领域&#xff0c;Flash和H5是两种主流的技术。Flash游戏曾经占据主导地位&#xff0c;但随着HTML5技术的发展和浏览器对Flash支持的逐渐减少&#xff0c;H5游戏逐渐成为主流。本教程将详细介绍Flash和H5的区别&#xff0c;并提供将Flash游戏转换为H5游戏…

如何查看电脑的屏幕刷新率?

1、按一下键盘的 win i 键&#xff0c;打开如下界面&#xff0c;选择【系统】&#xff1a; 2、选择【屏幕】-【高级显示设置】 如下位置&#xff0c;显示屏幕的刷新率&#xff1a;60Hz 如果可以更改&#xff0c;则选择更高的刷新率&#xff0c;有助于电脑使用起来界面更加流…

新书速览|循序渐进Node.js企业级开发实践

《循序渐进Node.js企业级开发实践》 1 本书内容 《循序渐进Node.js企业级开发实践》结合作者多年一线开发实践&#xff0c;系统地介绍了Node.js技术栈及其在企业级开发中的应用。全书共分5部分&#xff0c;第1部分基础知识&#xff08;第1&#xff5e;3章&#xff09;&#xf…

AUTOSAR AP和CP的安全要求规范(Safety Req)详细解读

一、规范的编制的背景原因 编制该规范的原因 确保系统安全性和可靠性 随着汽车电子系统日益复杂&#xff0c;功能不断增加&#xff0c;对安全性和可靠性的要求也越来越高。该规范为AUTOSAR平台在安全执行、配置、更新、信息交换、数据处理等多方面制定了明确要求&#xff0c;…

数仓技术hive与oracle对比(四)

问题处理 sqoop导入异常 将oracle数据库中的表&#xff0c;用sqoop导入hive时&#xff0c;如果表中字段值含有“&#xff0c;”&#xff0c;会导致导入hive后&#xff0c;每一行所有字段的内容都放在了第一个字段&#xff0c;其他字段均没有值。这是因为hive底层是以文件的形…

流网络等价性证明:边分解后的最大流保持不变

流网络等价性证明:边分解后的最大流保持不变 问题描述证明思路伪代码C 代码实现解释问题描述 在流网络中,证明将一条边分解为两条边所得到的是一个等价的网络。具体来说,假设流网络 $ G $ 包含边 $ (u, v) $,我们以如下方式创建一个新的流网络 $ G’ $: 创建一个新结点 $…

应用案例 | 船舶海洋: 水下无人航行器数字样机功能模型构建

水下无人航行器数字样机功能模型构建 一、项目背景 为响应水下装备系统研制数字化转型及装备系统数字样机建设的需要&#xff0c;以某型号水下无人航行器&#xff08;Underwater Unmanned Vehicle&#xff0c;UUV&#xff09;为例&#xff0c;构建UUV数字样机1.0功能模型。针对…

RabbitMQ七种工作模式之简单模式, 工作队列模式, 发布订阅模式, 路由模式, 通配符模式

文章目录 一. Simple(简单模式)公共代码:生产者:消费者: 二. Work Queue(工作队列模式)公共代码:生产者:消费者1, 消费者2(代码相同): 三. Publish/Subscribe(发布/订阅模式)公共代码:生产者:消费者: 四. Routing(路由模式)公共代码:消费者: 五. Topics(通配符模式)公共代码:生…

前端知识1html

VScode一些快捷键 Ctrl/——注释 !——生成html框架元素 *n——生成n个标签 直接书写html的名字回车生成对应的标签 常见标签 span&#xff1a; <span style"color: red;">hello</span> <span>demo</span> span实现&#xff1a; 标题…

Push an existing folder和Push an existing Git repository的区别

Push an existing folder 和 Push an existing Git repository 是在使用 Git 服务&#xff08;如 GitHub、GitLab、Bitbucket 等&#xff09;时两个常见的操作选项。它们的区别主要体现在项目的初始化和版本控制状态上&#xff1a; 1. Push an existing folder 适用场景&#…

Netty入门(快速了解以及使用netty)

二. Netty 入门 1. 概述 1.1 Netty 是什么&#xff1f; Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients.Netty 是一个异步的、基于事件驱动的网络应用框架&…

Zemax 中 ZBF 文件激光传播的描述

激光传播是指激光束在空间或介质中传播的方式。激光的独特特性&#xff0c;例如相干性、单色性和准直性&#xff0c;使其行为与普通光源不同。了解激光传播的原理在光学、通信、医疗技术和科学研究等领域至关重要。 激光产生高斯光束&#xff0c;其中强度在光束横截面上服从高…

DataSophon集成CMAK KafkaManager

本次集成基于DDP1.2.1 集成CMAK-3.0.0.6 设计的json和tar包我放网盘了. 通过网盘分享的文件&#xff1a;DDP集成CMAK 链接: https://pan.baidu.com/s/1BR70Ajj9FxvjBlsOX4Ivhw?pwdcpmc 提取码: cpmc CMAK github上提供了zip压缩包.将压缩包解压之后 在根目录下加入启动脚本…