C++智能指针

RAII

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源的技术

  • 不需要显示的释放资源
  • 对象的资源在其生命周期类保持有效

通常控制的资源:动态申请的内存、文件描述符、互斥量、网络连接等

在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象 。

智能指针原理

  • RAII特性

  • 具有像指针一样的行为

    (重载operator*()operator->()

template<class T>
class SmartPtr {
public:
	SmartPtr(T* ptr = nullptr)
		: _ptr(ptr)
	{}

    //RAII特性
	~SmartPtr()
	{
		if (_ptr)
		{
			delete _ptr;
		}
	}
    
	//具有像指针一样的行为
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
private:
	T* _ptr;
};

指针允许拷贝和赋值,使多个指针对象指向同一块资源,即浅拷贝。如果使用上述代码的SmartPtr,并且进行过拷贝赋值,那么很可能会导致野指针问题。这样的话对于SmartPtr的重点就在解决:

  • 智能指针对象的拷贝问题

auto_ptr

C++98中引入的智能指针

通过管理权转移的方式解决智能指针的拷贝问题,保证一个资源在任何时刻都只有一个对象在对其进行管理,这时同一个资源就不会被多次释放了

  • 拷贝构造函数中,用传入对象来初始化当前对象,并将传入对象管理资源的指针置空
  • 赋值运算符重载中,先将当前管理的资源释放,然后再接管传入对象管理的资源,并将传入对象管理资源的指针置空
template<class T>
class auto_ptr
{
public:
    auto_ptr(T* ptr = nullptr)
        : _ptr(ptr)
    {} 
    ~auto_ptr()
    {
        if (_ptr)
        {
            cout << "Delete:" << _ptr << endl;
            delete _ptr;
        }
    }
    T& operator*() { return *_ptr; }
    T* operator->() { return _ptr; }
    
    //智能指针对象的拷贝问题
    
    // 拷贝构造函数
    auto_ptr(auto_ptr<T>& ap)
        :_ptr(ap._ptr)
    {
        ap._ptr = nullptr;
    }

    // 赋值运算重载
    auto_ptr<T>& operator=(auto_ptr<T>& ap)
    {
        if (this != &ap)//防止自己给自己赋值的操作
        {
            if (_ptr)//释放原来管理的资源
            {
                delete _ptr;
            }
			//管理权转移
            _ptr = ap._ptr;
            ap._ptr = nullptr;
        }

        return *this;
    }
private:
    T* _ptr;
};

将一个auto_ptr对象赋值给另一个对象,原本的auto_ptr对象将拥有NULL指针,此时出现“悬垂指针”问题,比如:继续使用原对象,导致空指针解引用等问题。

由于这个缺陷,导致auto_ptr成为一种过时的智能指针,而后C++11中有更加安全可靠的选择。

unique_ptr

C++11中引入的智能指针

通过简单粗暴的防止对智能指针对象进行拷贝,这样也能保证资源不会被多次释放

  • 拷贝构造函数和赋值运算符被显式删除
template<class T>
class unique_ptr
{
// 防拷贝 C++98 只声明不实现 
//private:
	//unique_ptr(unique_ptr<T>& ap);
	//unique_ptr<T>& operator=(unique_ptr<T>& ap);
public:
	unique_ptr(T* ptr = nullptr)
		: _ptr(ptr)
	{}
	~unique_ptr()
	{
		if (_ptr)
		{
			cout << "Delete:" << _ptr << endl;
			delete _ptr;
		}
	}
	T& operator*() { return *_ptr; }
	T* operator->() { return _ptr; }
    
	// 防拷贝 C++11
	unique_ptr(unique_ptr<T>& ap) = delete;
	unique_ptr<T>& operator=(unique_ptr<T>& ap) = delete;
private:
	T* _ptr;
};

shared_ptr

C++11引入的更安全可靠的智能指针

通过引用计数的方式来实现多个shared_ptr对象之间共享资源

  • 每个被管理的资源都有一个引用计数,初始化为1
  • 当新对象管理该资源时,引用计数+1,当某对象不再管理该资源时,引用计数-1
  • 当该引用计数为0时,会将该资源释放
template<class T>
class shared_ptr
{
public:
	shared_ptr(T* ptr = nullptr)
		:_ptr(ptr)
		, _pcount(new int(1))
	{}

	T& operator*() { return *_ptr; }

	T* operator->() { return _ptr; }
    
	//析构后不再管理该资源
	~shared_ptr()
	{
		if (--(*_pcount) == 0)
		{
			if (_ptr != nullptr)
			{
				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) //引用计数--
			{
				delete _ptr;
				delete _pcount;
			}
            //管理同一块资源
			_ptr = sp._ptr;       
			_pcount = sp._pcount; 
			(*_pcount)++;         //引用计数++
		}
		return *this;
	}

private:
	T* _ptr;      //管理的资源
	int* _pcount; //管理的资源对应的引用计数
};

引用计数使用动态申请的空间

引用计数本身也是智能指针所管理/共享的资源,使用指针类型可以进行管理/共享。

如果使用int型变量,管理同一个资源时无法很好保证引用计数的一致性;如果使用static变量,同类的实例化对象其实是共享该静态成员的,即使管理的不是同一个资源。

引用计数++/- -操作的线程安全问题

可以使用mutex* _pmutex互斥锁来对引用计数进行加锁操作,同样的互斥锁也使用指针类型。

定制删除器

share_ptr并不是只管理以new方式申请到的内存空间,也可能是以new[]的方式申请到的空间,或管理的是一个文件指针。此时就需要使用特定的删除器

template<class T>
struct Delete
{
    void operator()(T* ptr)
    {
        //1. delete[] ptr
        //2. close(ptr)
        delete ptr;
    }
};

template<class T, class D = Delete<T>>
class shared_ptr
{
    void Release()
    {
        if (--(*_pCount) == 0)
        {
            D()(_ptr);	// 定制删除器
            delete _pCount;
        }
    }
    ~shared_ptr() { Release(); }
    。。。
};

循环引用问题

虽然在赋值运算符重载里面用_ptr != sp._ptr进行判断,避免管理同资源的对象间赋值,导致的重复计数问题。但是还是存在特殊的场景,诱发引用计数的错误----循环引用

struct ListNode
{
	shared_ptr<ListNode> _next;
	share_ptr<ListNode> _prev;
};
int main()
{
	shared_ptr<ListNode> node1(new ListNode);
	shared_ptr<ListNode> node2(new ListNode);
	//同类型(share_ptr)才能进行赋值拷贝
	node1->_next = node2;
	node2->_prev = node1;

	return 0;
}

在这里插入图片描述

只有当引用计数为0时资源才会释放。第一个节点引用计数变为0取决于第二个节点的prev指针,因此需要释放第二个节点,即:第二个节点的引用计数变0,这一步又取决于第一个节点的next指针…

因此第一个节点是释放取决于第二个节点,而第二个节点又需要第一个节点先释放。

weak_ptr

C++11引入的智能指针,可以与shared_ptr协同工作

在不增加对象引用计数的情况下观测和访问共享对象,不进行资源管理

  • 支持使用shared_ptr对象来进行构造/赋值
// 辅助型智能指针,使命配合解决shared_ptr循环引用问题
template<class T>
class weak_ptr
{
public:
	weak_ptr()
		:_ptr(nullptr)
	{}
	T& operator*() { return *_ptr; }
	T* operator->() { return _ptr; }
    
    //用share_ptr构建对象
	weak_ptr(const shared_ptr<T>& sp)
		:_ptr(sp.get())
	{}

	weak_ptr(const weak_ptr<T>& wp)
		:_ptr(wp._ptr)
	{}
    //用share_ptr对象进行赋值拷贝
	weak_ptr<T>& operator=(const shared_ptr<T>& sp)
	{
		_ptr = sp.get();
		return *this;
	}
public:
	T* _ptr;
};

对于shared_ptr问题进行修改

struct ListNode
{
	weak_ptr<ListNode> _next;
	weak_ptr<ListNode> _prev;
};
int main()
{
	shared_ptr<ListNode> node1(new ListNode);
	shared_ptr<ListNode> node2(new ListNode);
	//weak_ptr支持shared_ptr类型的赋值拷贝
	node1->_next = node2;
	node2->_prev = node1;

	return 0;
}

希望暂时访问一个shared_ptr所指向的对象而不想把它引用计数加1时,可以使用weak_ptr代替 shared_ptr。


    🦀🦀观看~~

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

多线程/std::thread线程退出方式详解

文章目录 概述不 join 也不 detach执行了detach并不能万事大吉建议使用 join 函数 概述 这里默认你已经了解 std::thread 类的基本使用&#xff0c;和WinAPI多线程编程中 “如何优雅的退出线程” 等相关知识。阅读该文前&#xff0c;建议先看看《多线程 /C 11 std::thread 类深…

python、pyqt5实现人脸检测、性别和年龄预测

摘要&#xff1a;这篇博文介绍基于opencv&#xff1a;DNN模块自带的残差网络的人脸、性别、年龄识别系统&#xff0c;系统程序由OpenCv, PyQt5的库实现。如图系统可通过摄像头获取实时画面并识别其中的人脸表情&#xff0c;也可以通过读取图片识别&#xff0c;本文提供完整的程…

【IIS建站教程】windows本地搭建web服务,内网穿透发布公网访问

✨个人主页&#xff1a;bit me&#x1f447; 目 录 &#x1f43e;1.前言&#x1f490;2.Windows网页设置&#x1f338;2.1 Windows IIS功能设置&#x1f337;2.2 IIS网页访问测试 &#x1f340;3. Cpolar内网穿透&#x1f339;3.1 下载安装Cpolar&#x1f33b;3.2 Cpolar云端设…

【Leetcode60天带刷】day36——56. 合并区间,738.单调递增的数字

​ 题目&#xff1a; 56. 合并区间 以数组 intervals 表示若干个区间的集合&#xff0c;其中单个区间为 intervals[i] [starti, endi] 。请你合并所有重叠的区间&#xff0c;并返回 一个不重叠的区间数组&#xff0c;该数组需恰好覆盖输入中的所有区间 。 示例 1&#xff1a;…

菜鸡shader:L4三色环境光原理妙用并在ue4中实现

三色环境光的拓展运用 我的上一篇博客写了关于三色环境光的原理&#xff0c;这次就来简单拓展一下。最重要的核心思想其实就是取法线向量的第二个分量&#xff0c;因为它控制方法是指向xz平面的上或者下。 所以这次要用这个原来来单独摘出上层环境光&#xff0c;乘上菲涅尔&a…

ASP.NET Core Web API之Token验证

在实际开发中&#xff0c;我们经常需要对外提供接口以便客户获取数据&#xff0c;由于数据属于私密信息&#xff0c;并不能随意供其他人访问&#xff0c;所以就需要验证客户身份。那么如何才能验证客户的身份呢&#xff1f;今天以一个简单的小例子&#xff0c;简述ASP.NET Core…

交叉熵、Focal Loss以及其Pytorch实现

交叉熵、Focal Loss以及其Pytorch实现 本文参考链接&#xff1a;https://towardsdatascience.com/focal-loss-a-better-alternative-for-cross-entropy-1d073d92d075 文章目录 交叉熵、Focal Loss以及其Pytorch实现一、交叉熵二、Focal loss三、Pytorch1.[交叉熵](https://pyto…

Python 动态生成系统数据库设计到word文档

背景 经常需要交付一些系统文档而且基本都是word的&#xff0c;其中又有系统数据库介绍模块&#xff0c; 看着数据库里的几百张表于是我开始怀疑人生, 所以咱手写一个 涉及知识 pymysql 操作数据库 -tkinter GUI图形库threading 线程queue 阻塞队列pandas python数据计算…

layui(5)——内置模块分页模块

模块加载名称&#xff1a;laypage laypage 的使用非常简单&#xff0c;指向一个用于存放分页的容器&#xff0c;通过服务端得到一些初始值&#xff0c;即可完成分页渲染&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset&quo…

RocketMQ --- 实战篇

一、案例介绍 1.1、业务分析 模拟电商网站购物场景中的【下单】和【支付】业务 1.1.1、下单 流程 用户请求订单系统下单 订单系统通过RPC调用订单服务下单 订单服务调用优惠券服务&#xff0c;扣减优惠券 订单服务调用调用库存服务&#xff0c;校验并扣减库存 订单服务调…

长尾关键词有什么作用?要怎么用?

长尾关键词很多的网站都会忽略其存在&#xff0c;其实你不要小看长尾关键词&#xff0c;他将带给网站的流量也是极其可观的&#xff0c;所说比不上那些重点关键词的流量&#xff0c;但是对提升网站的权重还是有着重要的作用。 长尾关键词有什么用&#xff1f;长尾关键词的3…

Gitlab群组及项目仓库搭建

1、新建群组 2、新建项目 3、克隆到Visualstudio 复制克隆地址&#xff0c;克隆到本地 这里会让你登录账号 可以添加成员并邀请ta进项目组 从已注册用户列表中选择 4、Git工作流 回顾一下Git工作流&#xff0c;工程人员只需要从Develop分支新建自己的分支即可。分支命名以姓名…

CadLib 4.0.2023.31601 net for Windows Crack

CadLib 4.0 for Windows&#xff1a;在 C# VB .NET 中读取、写入和显示 AutoCAD DWG 和 DXF 文件 CadLib 4.0 for Windows仅在Windows上运行&#xff0c;并且基于.NET 4.x。 CadLib 4.0读取、写入和显示 C#、VB.NET 或任何其他 .NET 语言的 AutoCAD™ DWG 和 DXF 文件。下载试…

2-css-3

一 选择器 1 结构伪类选择器 作用&#xff1a;根据元素的结构关系查找元素。 选择器说明E:first-child查找第一个E元素E:last-child查找最后一个E元素E:nth-child(N)查找第N个E元素&#xff08;第一个元素N值为1&#xff09; li:first-child {background-color: green; }2 :…

5.6.3 套接字

5.6.3 套接字 我们先以示例引入套接字的基本内容&#xff0c;我们知道在邮政通信的时候我们需要在信封上写明我们的收件地址&#xff0c;比如北京市海淀区双清路30号清华大学8444号某某某收&#xff0c;这其中我们需要一个物理地址“北京市海淀区双清路30号”&#xff0c;一个…

6.22 驱动开发作业

字符设备驱动内部实现原理 1.字面理解解析&#xff1a; 字符设备驱动的内部实现有两种情况&#xff1a; 情况1.应用层调用open函数的内部实现&#xff1a; open函数的第一个参数是要打开的文件的路径&#xff0c;根据这个路径 虚拟文件系统层VFS 可以找到这个文件在文件系统…

openeuler22.03系统salt-minion启动报“Invalid version: ‘cpython‘“错的问题处理

某日&#xff0c;检查发现一台openeuler22.03 SP1系统的服务器上之前正常运行的saltstack客户端minion未运行&#xff0c;查看服务状态&#xff0c;报"Invalid version: cpython"错&#xff0c;无法正常运行&#xff0c;本文记录问题处理过程。 一、检查salt-minion…

uniapp中小程序的生命周期

一、uni-app应用生命周期 函数名说明onLuaunch当uni-app 初始化完成时触发&#xff08;全局只触发一次&#xff09;onShow当 uni-app 启动&#xff0c;或从后台进入前台显示onHide当 uni-app 从前台进入后台onError当 uni-app 报错时触发onUniNViewMessage对 nvue 页面发送的数…

android jetpack Room的基本使用(java)

数据库的基本使用 添加依赖 //roomdef room_version "2.5.0"implementation "androidx.room:room-runtime:$room_version"annotationProcessor "androidx.room:room-compiler:$room_version"创建表 Entity表示根据实体类创建数据表&#xff0c…

发送图文并茂的html格式的邮件

本文介绍如何生成和发送包含图表和表格的邮件&#xff0c;涉及echarts图表转换为图片、图片内嵌到html邮件内容中、html邮件内容生成、邮件发送方法等 一、图表处理 因为html格式的邮件不支持echarts,也不支持js执行&#xff0c;所以图表需要转换为图片内嵌在邮件内容中 因为平…