C++_智能指针详解

news2024/11/30 8:53:49

什么是智能指针?为什么要有智能指针?到目前为止,我们编写的程序所使用的对象都有着严格定义的生命周期。比如说,全局对象在程序启动时分配,在程序结束时销毁;再比如说局部static对象在第一次使用前分配,在程序结束时销毁......除了这些对象,C++还支持动态分配对象。动态分配的对象的生命周期与它们在哪里创建无关,只有当显式地被释放时,这些对象才会被销毁

如果不显式地释放,则很有可能造成内存泄漏!!而动态对象的正确释放是编程中最容易出错的地方,所以C++引入了智能指针的概念,来帮助我们编程人员更好的释放。

了解内存泄漏与其危害

内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死!

智能指针的使用及原理

RAII

RAII(Resource Acquisition Is Initialization,资源获得即初始化)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。

对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构时释放资源。因此,我们实际上就是把管理一份资源的责任托管给了一个对象。这种做法有两大好处:

  • 不需要显式地释放资源(因为析构函数是自动调用的)。
  • 采用这种方式,对象所需的资源在其生命周期内始终保持有效
// 使用RAII思想设计的SmartPtr类
template<class T>
class SmartPtr
{
public:
	SmartPtr(T* ptr)        
		:_ptr(ptr)             // 把需要管理的对象托管在成员变量中
	{}
	~SmartPtr()
	{
        if(_ptr)
		    delete _ptr;       // 当该类析构的时候,自动清理资源
	}
private:
	T* _ptr;
};
double Division()
{
    int a, b;
    cin >> a >> b;
	if (b == 0)                // 当b == 0时抛出异常
		throw "Division by zero condition!";
	return (double)a / (double)b;
}
void Func()
{
    ShardPtr<int> sp1(new int);        // 动态创建对象
    ShardPtr<int> sp2(new int);
    cout << Division() << endl;
}
int main()
{
    try 
        Func();
    catch(const exception& e)
        cout<<e.what()<<endl;
    return 0;
}

智能指针的原理

上面代码中的SmartPtr类可以说是智能指针吗?不,还不是!既然叫做指针,那么必须要具有指针的各种行为。比如说:指针可以解引用,也可以通过->去访问所指空间中的内容,因此,还应在SmartPtr类中添加类似以下功能的代码:

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

总而言之,智能指针的原理应包括以下两点:

  • RAII特性。
  • 重载operator*和opertaor->,具有像指针一样的行为。

 std::auto_ptr

在C++98版本的库中提供了auto_ptr的智能指针。auto_ptr在实现原理上使用了管理权转移的思想。

// Date为日期类
auto_ptr<Date> ap1(new Date);
auto_ptr<Date> ap2(ap1);        // 拷贝,管理权转移
// ap1的所有权都给了ap2,所以ap1现在什么都没有
// 访问ap1的任何内容都会报错
ap1->_year++;                   // 报错

auto_ptr<Date> ap2(ap1)构造拷贝出ap2,ap2完全夺取了ap1的管理权,进而导致ap1无家可归,进行 ap1->访问时程序就会报错。同样道理,当进行了ap2 = ap1,程序也存在这样的问题,原因依旧在于ap1被彻彻底底的夺走了一切,所以这种编程思想是十分危险的。总之,auto_ptr是一个失败设计,很多公司明确要求禁止使用auto_ptr。
 

 std::unique_ptr

unique_ptr的实现原理很简单,就是简单粗暴的防拷贝。unique_ptr类中的拷贝和赋值都被禁掉了。

template<class T>
class unique_ptr
{
public:
    unique_ptr(T* ptr)
        :_ptr(ptr)
    {}
    ~unique_ptr()
    {
        if (_ptr)
        {
            cout << "delete:" << _ptr << endl;
            delete _ptr;
        }
    }
    // 像指针一样使用
    T& operator*()    {return *_ptr;}
    T* operator->()    {return _ptr;}
    // 被禁掉的拷贝构造和赋值
    unique_ptr(const unique_ptr<T>& sp) = delete;
    unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;
private:
    T* _ptr;
};

上面的unique_ptr用于处理单个的对象,而new/delete和new[]/delete[]底层实现机制又是不同的,对于new[]出来的多个对象unique_ptr又给出了特化的版本,请点击unique_ptr文档

// Date为日期类
// 处理单个对象
unique_ptr<Date> up1(new Date);
// 处理多个对象
unique_ptr<Date[]> up2(new Date[5]);

std::shared_ptr

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

  1. shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
  2. 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。
  3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
  4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。

看着上面的理论吧啦吧啦的,我个人感觉不是很好理解,结合图示和模拟实现的代码可能好理解一点,下图是拷贝的过程原理,赋值类似。

// 基础版的shared_ptr模拟实现
template<class T>
class shared_ptr
{
public:
	shared_ptr(T* ptr)
		:_ptr(ptr)
		, _pcount(new int(1))	// 这里新开一块空间,用作计数
	{}
	// s2(s1)
	shared_ptr(const shared_ptr<T>& sp)
		:_ptr(sp._ptr)
		,_pcount(sp._pcount)
	{
		++(*_pcount);
	}
	// sp3 = sp1
	shared_ptr<T>& operator=(const shared_ptr<T>& sp)
	{
		if (_ptr != sp._ptr)
		{
			release();			// 清理sp3之前指向的空间

			_ptr = sp._ptr;
			_pcount = sp._pcount;
			++(*_pcount);
		}
		return *this;
	}
    void release()
	{
		if (--(*_pcount) == 0)
		{
			delete _ptr;
			delete _pcount;
			_ptr = nullptr;
			_pcount = nullptr;
		}
	}
	~shared_ptr()
	{
		release();
	}

    // 相应的指针行为 
	T* get()           {return _ptr;}
	int use_count()    {return *_pcount;}
	T& operator*()     {return *_ptr;}
	T* operator->()    {return _ptr;}
private:
	T* _ptr = nullptr;
	int* _pcount;
};

注:shared_ptr不需要显式的实现移动构造和移动赋值。

循环引用

尽管shared_ptr是一种比较完美的编程思想,但是再完美也会有一定的瑕疵,而这个瑕疵-就是循环引用。一般情况下,循环引用是不会轻易遇到的,如果遇到了,那你就自认倒霉叭~~~

我们先来认识一下循环引用,请看代码

// 循环引用的场景
struct ListNode
{
	int _data;
	shared_ptr<ListNode> _prev;
	shared_ptr<ListNode> _next;

	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};
int main()
{
	shared_ptr<ListNode> n1(new ListNode);
	shared_ptr<ListNode> n2(new ListNode);

	n1->_next = n2;    // n1指向n2,语句1
	n2->_prev = n1;    // n2指向n1,语句2

	return 0;//输出结果为空
}

上述代码中,出现循环引用的主要原因就是语句1与语句2同时存在。这两行代码存在其中的任何一行都不会有问题,怕的就是两行代码同时存在!!!它们同时存在会构成循环引用,循环引用会导致内存泄漏。

为了解决上述情况,C++11引入了weak_ptr,weak_ptr不同于上述智能指针,它不支持管理资源,只是与shared_ptr配合使用。严格来说,weak_ptr不是智能指针,因为它不支持RAII。只要将上述代码中的ListNode类中的shared_ptr改为weak_ptr,循环引用就可以得到很好的解决:

struct ListNode
{
	int _data;
    // 改为weak_ptr
	weak_ptr<ListNode> _prev;
	weak_ptr<ListNode> _next;

	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};

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

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

相关文章

4.5章节python中的break和continue语句的作用

在Python中&#xff0c;break 和 continue 是两个用于控制循环流程的关键字。它们提供了在特定条件下提前退出循环或跳过当前迭代并进入下一次迭代的机制。 一、break语句 break 语句用于立即终止当前的循环&#xff08;无论是 for 循环还是 while 循环&#xff09;&#xff…

最佳人力资源管理工具,6款热门产品功能对比

文章介绍了ZohoPeople、北森、i人事等六款主流人力资源管理系统&#xff0c;涵盖招聘、培训、考勤等功能&#xff0c;各有特点&#xff0c;适合不同规模企业需求。建议企业试用后选择&#xff0c;提高管理效率。 一、Zoho People Zoho People是一款强大的云端人力资源管理系统…

看Threejs好玩示例,学习创新与技术(LiquidRaymarching)

今天的示例有点超出我的想象&#xff0c;首先会科普下WGSL这种新的着色器脚本&#xff0c;然后说说示例《Liquid Raymarching Scene with Three.js Shading Language | Codrops (tympanus.net)》的技术流程。本示例最终呈现的效果如下。可以看到他跟QQ那个消息拖拽消灭的效果非…

Flink 03 | 数据流基本操作

Flink数据流结构 DataStream 转换 通常我们需要分析的业务数据可能存在如下问题&#xff1a; 数据中包含一些我们不需要的数据 数据格式不方面分析 因此我们需要对原始数据流进行加工&#xff0c;比如过滤、转换等操作才可以进行数据分析。 “ Flink DataStream 转换主要作…

C++ -引用-详解

博客主页&#xff1a;【夜泉_ly】 本文专栏&#xff1a;【C】 欢迎点赞&#x1f44d;收藏⭐关注❤️ C -引用-详解 1.引用基础1.1是什么1.2特点 2.引用的意义3.引用的应用场景3.1作为参数3.2作为返回值传值返回引用返回 4.权限问题5.与指针的区别6.总结 1.引用基础 1.1是什么 …

SpringBoot整合异步任务执行

同步任务&#xff1a; 同步任务是在单线程中按顺序执行&#xff0c;每次只有一个任务在执行&#xff0c;不会引发线程安全和数据一致性等 并发问题 同步任务需要等待任务执行完成后才能执行下一个任务&#xff0c;无法同时处理多个任务&#xff0c;响应慢&#xff0c;影响…

小红书三面被问 RAG 原理,秒挂…

最近这一两周看到不少互联网公司都已经开始秋招发放Offer。 不同以往的是&#xff0c;当前职场环境已不再是那个双向奔赴时代了。求职者在变多&#xff0c;HC 在变少&#xff0c;岗位要求还更高了。 最近&#xff0c;我们又陆续整理了很多大厂的面试题&#xff0c;帮助一些球…

MySQL高阶2082-富有客户的数量

目录 题目 准备数据 分析数据 题目 编写解决方案找出 至少有一个 订单的金额 严格大于 500 的客户的数量。 准备数据 Create table If Not Exists Store (bill_id int, customer_id int, amount int)Truncate table Storeinsert into Store (bill_id, customer_id, amoun…

openpnp - 图像传送方向要在高级校正之前设置好

文章目录 openpnp - 图像传送方向要在高级校正之前设置好笔记END openpnp - 图像传送方向要在高级校正之前设置好 笔记 图像传送方向和JOG面板的移动控制和实际设备的顶部摄像头/底部摄像头要一致&#xff0c;这样才能和贴板子时的实际操作方向对应起来。 设备标定完&#xf…

(C语言贪吃蛇)16.贪吃蛇食物位置随机(完结撒花)

目录 前言 修改方向 修改内容 效果展示 两个新的问题&#x1f64b; 1.问题1 2.问题2 代码如下&#xff1a; 前言 我们上一节实现了贪吃蛇吃食物身体节点变长&#xff0c;但是食物的刷新位置不是随机的&#xff0c;并且初始化几次后食物就刷不见了&#xff0c;本节我们就来…

【Linux】进程地址空间、环境变量:从理论到实践(三)

&#x1f308; 个人主页&#xff1a;Zfox_ &#x1f525; 系列专栏&#xff1a;Linux 目录 &#x1f680; 前言一&#xff1a;&#x1f525; 环境变量 &#x1f95d; 基本概念&#x1f95d; 常见环境变量&#x1f95d; 查看环境变量方法 二&#xff1a;&#x1f525; 测试 &…

Stable Diffusion绘画 | 插件-Deforum:动态视频生成(中篇)

本篇文章重点讲解参数最多的 关键帧 模块。 「动画模式」选择「3D」&#xff1a; 下方「运动」Tab 会有一系列参数&#xff1a; 以下4个参数&#xff0c;只有「动画模式」选择「2D」才会生效&#xff0c;可忽略&#xff1a; 运动 平移 X 让镜头左右移动&#xff1a; 大于0&a…

JavaSE——面向对象练习题

1.对象数组排序 定义一个Person类{name,age,job}&#xff0c;初始化Person对象数组&#xff0c;有3个person对象&#xff0c;并按照age从小到大进行冒泡排序&#xff1b;再按照name的长度从小到大进行选择排序。 public class HomeWork01 {public static void main(String[] a…

基于spring boot的篮球论坛系统

作者&#xff1a;计算机搬砖家 开发技术&#xff1a;SpringBoot、php、Python、小程序、SSM、Vue、MySQL、JSP、ElementUI等&#xff0c;“文末源码”。 专栏推荐&#xff1a;SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;Java精选实战项…

基于拥堵模型的轻量级平台公交室内情况监控系统

论文标题&#xff1a;Bus Indoor Situation Monitoring System Based on Congestion Model Using Lightweight Platform 作者信息&#xff1a;Dong Hyun Kim, Yun Seob Kim, 和 Jong Deok Kim* 所属机构&#xff1a;Pusan National University, Department of Computer Scienc…

Linux·环境变量与进程地址空间

1. 命令行参数 各位可能见过main函数也是有参数的&#xff0c;只是我们平时写的代码都比较简单&#xff0c;用不到main函数的参数&#xff0c;下面我们看一下main函数的参数是什么又是怎么用的 我们看这样一段代码 其编译运行后的效果是这样的 我们将main函数后面的那两个参数叫…

排序算法剖析

文章目录 排序算法浅谈参考资料评价指标可视化工具概览 插入排序折半插入排序希尔排序冒泡排序快速排序简单选择排序堆排序归并排序基数排序 排序算法浅谈 参考资料 数据结构与算法 评价指标 稳定性&#xff1a;两个相同的关键字排序过后相对位置不发生变化时间复杂度空间复…

MyBatisPlus——学习笔记

MyBatisPlus 一、导入依赖 <!-- MyBatisPlus --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.2</version></dependency><!-- MySql --><de…

C++基础(7)——STL简介及string类

目录 1.STL简介 1.1什么是 1.2STL的历史版本 1.3STL的六大组件 ​编辑 1.4有用的网址 2.string类 2.1string的多种定义方式 2.2string的插入 2.2.1尾插&#xff08;push_back&#xff09; 2.2.2insert插入 2.3拼接&#xff08;append&#xff09; 2.4删除 2.4.1尾…

CoRL 2024 麻省理工学院提出T3触觉Transformer,打破触觉感知的壁垒,重塑未来机器人

在智能机器人领域&#xff0c;触觉感知的研究正逐渐成为关注的焦点。然而&#xff0c;如何让机器人通过触觉更智能地感知和操作&#xff0c;依然是一个未解决的挑战。基于相机的触觉感知是一种通过在软弹性体下嵌入相机来捕获与环境的细粒度交互的感知方法&#xff0c;是最流行…