C++|智能指针

目录

引入

一、智能指针的使用及原理

1.1RAII

1.2智能指针原理

1.3智能指针发展

1.3.1std::auto_ptr

1.3.2std::unique_ptr

1.3.3std::shared_ptr 

二、循环引用问题及解决方法

2.1循环引用

2.2解决方法 

三、删除器

四、C++11和boost中智能指针的关系 


引入

回顾上一篇章,学习了异常机制,但面临了一种情况还没有解决,就是异常带来的内存泄漏,如下:

#include <iostream>
using namespace std;double Division(int a, int b)
{// 当b == 0时抛出异常if (b == 0)throw "Division by zero condition!";//抛出的异常是字符串elsereturn ((double)a / (double)b);
}
void Func() 
{int* p = new int[1];int len, time;cin >> len >> time;cout << Division(len, time) << endl;delete[] p;}int main() {try {Func();}catch (const char* errmsg) {cout << "Caught in main: " << errmsg << endl;}return 0;
}

调用Division函数时,若抛出异常,最终是被main函数中的catch所捕获,直接进入到main函数中的catch中了,但是,delete[] p不会被执行了 ,导致的问题就是内存泄漏。那么可以通过智能指针来解决这个问题

一、智能指针的使用及原理

1.1RAII

RAII是一种利用对象生命周期来控制程序资源的简单技术。在对象构造时获取对象资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候由编译器顺带释放资源。实际上把控资源就是管理对象。

这种做法体现的优势是:

  • 不需要显示地释放资源
  • 采用这种方式,对象所需的资源在其生命期内始终保持有效

RAII是一种设计思想,而智能指针正是采用了这种思想。

RAII思想设计: 

#include <iostream>
using namespace std;// 使用RAII思想设计的SmartPtr类
template<class T>
class SmartPtr {
public:SmartPtr(T* ptr = nullptr):_ptr(ptr){}~SmartPtr(){if (_ptr)delete _ptr;}private:T* _ptr;
};
double Division(int a, int b)
{// 当b == 0时抛出异常if (b == 0)throw "Division by zero condition!";//抛出的异常是字符串elsereturn ((double)a / (double)b);
}
void Func()
{int* p = new int[1];int len, time;cin >> len >> time;cout << Division(len, time) << endl;SmartPtr<int> sp(p);//当Division抛异常时,会被main函数中的catch捕获,那么程序会直接跳到main函数所在的catch//对此了,Func函数的栈帧会自动销毁,对于内置类型会自动释放,对于自定义类型而言会去调用它的析构函数//所以sp会去调用析构函数,同时释放了p的资源}int main() {try{Func();}catch (const char* errmsg){cout << "Caught in main: " << errmsg << endl;}return 0;
}

输出结果:

1.2智能指针原理

上述的SmartPtr虽然是通过RAII思想设计的类,但还不能真正称作为智能指针,因为它还不有指针的行为。指针可以解引用,也可以通过->去访问所指空间的内容,因此还需要重载*和->。

#include <iostream>
using namespace std;
template<class T>
class SmartPtr {
public:SmartPtr(T* ptr = nullptr): _ptr(ptr){}~SmartPtr(){if (_ptr)delete _ptr;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}
private:T* _ptr;
};
struct Date
{int _year;int _month;int _day;
};
int main()
{SmartPtr<int> sp1(new int);*sp1 = 10;cout << *sp1 << endl;SmartPtr<Date> sparray(new Date);sparray->_year = 2024;sparray->_month = 7;sparray->_day = 7;cout << (*sparray)._year << ":" << (*sparray)._month << ":" << (*sparray)._day << endl;return 0;
}

输出结果:

总结一下,智能指针是通过RAII思想设计出的类,它重载operator*和operator->,具有像指针一样的行为。 

1.3智能指针发展

对于上述代码,却有一个弊端,如下:

int main()
{SmartPtr<int> sp1(new int);SmartPtr<int> sp2(sp1);return 0;
}

 带来的问题是,sp1拷贝给sp2是一个浅拷贝,所以sp2._ptr和sp1._ptr指向同一块空间,在析构时候,sp2._ptr所指向空间先释放,随后释放sp1._ptr所指向空间,但是该空间已被释放,所以会报错。那么为了解决这个问题,在历史上,智能指针有了一定的发展,来看。

1.3.1std::auto_ptr

C++98版本库中提供了auto_ptr的智能指针。他上述问题的实现原理是:通过管理权转移思想。

// C++98 管理权转移 auto_ptr
#include <iostream>
using namespace std;
namespace bit
{template<class T>class auto_ptr{public:auto_ptr(T* ptr):_ptr(ptr){}//ap2(ap1)auto_ptr(auto_ptr<T>& sp):_ptr(sp._ptr){// 管理权转移sp._ptr = nullptr;//相当于ap1._ptr不再管理资源了,被置空了}//ap2= ap1auto_ptr<T>& operator=(auto_ptr<T>& ap){// 检测是否为自己给自己赋值if (this != &ap){// 释放当前对象中资源if (_ptr)delete _ptr;// 转移ap中资源到当前对象中_ptr = ap._ptr;ap._ptr = NULL;}return *this;}~auto_ptr(){if (_ptr){cout << "delete:" << _ptr << endl;delete _ptr; }}// 像指针一样使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};
}int main()
{std::auto_ptr<int> sp1(new int);std::auto_ptr<int> sp2(sp1); // 管理权转移,sp1不再管理资源了,在析构的时候,确实没啥问题//但带来了另一个问题,sp1所管理资源,被置空了,即sp1._ptr1 ==  nullptr,但对于外人来说并不知道//若有人使用已被释放的资源,如下:*sp2 = 10;cout << *sp2 << endl;cout << *sp1 << endl;//对空指针解引用,报错return 0;
}

auto_ptr指针虽然解决了析构带来的问题,但是带来了新的问题,采用管理权转移思想,将资源由最后一个拷贝对象管理,而被拷贝对象都被置空了,导致有人使用被置空的拷贝对象时,会出现对空指针解引用,报错。所以该智能指针也可以算是一个"失败的man",很多公司也明确要求不能使用它。

1.3.2std::unique_ptr

随着C++的发展,C++11提供了更靠谱的智能指针unique_ptr,其实现原理:简单粗暴的防拷贝。

#include <iostream>
using namespace std;
namespace bit
{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(unique_ptr<T>& sp);//c++11仿拷贝/*unique_ptr(const unique_ptr<T>& sp) = delete;unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;*/private:T* _ptr;//将拷贝私有化可以防拷贝,但这是c++98用法//unique_ptr(unique_ptr<T>& sp);};
}//类外实现拷贝
//template<class T>
//bit::unique_ptr<T>::unique_ptr(bit:: unique_ptr<T>& sp)
//{
//	//...
//}int main()
{bit::unique_ptr<int> sp1(new int);//bit::unique_ptr<int> sp2(sp1);//拷贝构造被删除了,编译器也不会默认生成了return 0;
}

 由于前面出现的问题都是因为拷贝带来的,那么unique_ptr了就直接把拷贝给禁了,不让你因为各种操作而导致额外的问题,这确实解决了。但是不能使用拷贝了,这也不是办法呀

1.3.3std::shared_ptr 

随着前面智能指针问题的暴露,C++11不断发展,最终也完善了该问题,提出了更靠谱且支持拷贝的shared_ptr。其原理:是通过引用计数方式来实现多个shared_ptr对象之间共享资源。

其满足以下规则:

1.每个shared_ptr对象,其都包含着一个指针,该指针所指向空间内容用来计数有多少个指针指向该空间,即有多少个对象管理同一份资源,当多个对象管理同一份资源,那么他们的指针就会指向同一份空间,空间内容记录着这些数量,每增加一个对象管理相同资源,则计数也会加1

2.当对象销毁时即调用析构函数,就说明该对象不再管理这份资源,其对应的指针不再指向计数的空间,引用计数就会减1

3.如果引用计数是0,就说明没有对象管理该资源了,则必须释放该资源,相反地,如果不是0,说明还有对象管理资源,则就不能释放该资源,否则,对于其他对象中的指针就成野指针了。

#include <iostream>
using namespace std;namespace bit
{template<class T>class shared_ptr{public:shared_ptr(T* ptr = nullptr):_ptr(ptr), _pcount(new int(1)){}shared_ptr(const shared_ptr<T>& sp){if (this != &sp){_ptr = sp._ptr;_pcount = sp._pcount;(*_pcount)++;}}shared_ptr<T>& operator=(const shared_ptr<T>& sp){if (this != &sp){if (--(*sp._pcount) == 0){delete _ptr;delete _pcount;}_ptr = sp._ptr;_pcount = sp._pcount;(*_pcount)++;}return *this;}~shared_ptr(){if (--(*_pcount) == 0){delete _ptr;delete _pcount;_ptr = nullptr;_pcount = nullptr;}}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;int* _pcount;};
}

调试结果: 

shared_ptr解决了拷贝带来的问题,不幸的是,又出来了新的问题,那就是在线程安全和循环引用问题,对于线程问题,先放放,那我们要讲的是循环引用问题。

二、循环引用问题及解决方法

2.1循环引用

根据上述shared_ptr的代码,在外头定义一个类,进行以下操作:

struct ListNode
{bit::shared_ptr<ListNode> _next;bit::shared_ptr<ListNode> _prev;~ListNode(){cout << "~ListNode()" << endl;}
};
int main()
{bit::shared_ptr<ListNode> node1(new ListNode);bit::shared_ptr<ListNode> node2(new ListNode);node1->_next = node2;node2->_prev = node1;return 0;
}

输出结果:

没有任何结果,而当屏蔽node1->_next = node2;或者node2->_prev = node1;中的任何一句,

其输出结果:

这是为何?如图:

两边的节点各自受到_next,_prev牵连,造成了一个循环,且引用计数一直维持在1,并没有减到0,只要屏蔽其中一条语句就不会受到限制,正常析构。虽然这个智能指针是模拟的,就算是库里面的也有一样的问题。

2.2解决方法 

那么为了解决该问题,专门引入了一个智能指针来处理--weak_ptr

#include <iostream>
using namespace std;namespace bit
{template<class T>class shared_ptr{public:shared_ptr(T* ptr = nullptr):_ptr(ptr), _pcount(new int(1)){}shared_ptr(const shared_ptr<T>& sp){if (this != &sp){_ptr = sp._ptr;_pcount = sp._pcount;++(*_pcount);}}shared_ptr<T>& operator=(const shared_ptr<T>& sp){if (this != &sp){if (--(*_pcount) == 0){delete _ptr;delete _pcount;}_ptr = sp._ptr;_pcount = sp._pcount;++(*_pcount);}return *this;}~shared_ptr(){if (--(*_pcount) == 0){delete _ptr;delete _pcount;_ptr = nullptr;_pcount = nullptr;}}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T* get() const{return _ptr;}private:T* _ptr;int* _pcount;};// 简化版本的weak_ptr实现template<class T>class weak_ptr{public:weak_ptr():_ptr(nullptr){}weak_ptr(const shared_ptr<T>& sp):_ptr(sp.get()){}weak_ptr<T>& operator=(const shared_ptr<T>& sp){_ptr = sp.get();return *this;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};}struct ListNode
{bit::weak_ptr<ListNode> _next;bit::weak_ptr<ListNode> _prev;~ListNode(){cout << "~ListNode()" << endl;}
};int main()
{bit::shared_ptr<ListNode> node1(new ListNode);bit::shared_ptr<ListNode> node2(new ListNode);node1->_next = node2;node2->_prev = node1;return 0;
}

 输出结果:

那么weak_ptr是怎样解决问题的了,其实很简单,weak_ptr不在对引用计数进行管理了,所以shared_ptr对象初始化时的引用计数都是为1,在进行析构的时候,引用计数都会减到0,然后释放对应的_ptr,_pcount。

三、删除器

对于智能指针的析构还有一个问题,就是对于不是通过new出来的对象或者说new出来的对象是带有[]的如何通过智能指针进行管理,总不能每一种情况来一份代码。那么shared_ptr设计了一个删除器来解决这个问题。

// 仿函数的删除器#include <iostream>
using namespace std;template<class T>
struct FreeFunc {void operator()(T* ptr){cout << "free:" << ptr << endl;free(ptr);}
};
template<class T>
struct DeleteArrayFunc {void operator()(T* ptr){cout << "delete[]" << ptr << endl;delete[] ptr;}
};
int main()
{FreeFunc<int> freeFunc;shared_ptr<int> sp1((int*)malloc(4), freeFunc);DeleteArrayFunc<int> deleteArrayFunc;shared_ptr<int> sp2(new int[4], deleteArrayFunc);shared_ptr<FILE> sp5(fopen("test.txt", "w"), [](FILE* p){fclose(p); });return 0;
}

输出结果:

如上代码,删除器可以通过自己定义的方式来释放空间,这只是展示了仿函数删除器,仅仅是九牛一毛。

四、C++11和boost中智能指针的关系 

Boost库是为C++语言标准库提供扩展的一些C++程序库的总称,是一个"准"标准库,由Boost社区组织开发、维护。Boost库可以与C++标准库完美共同工作,并且为其提供扩展功能。

1.C++98 中产生了第一个智能指针auto ptr.

不好的设计,对象悬空(不建议使用)
2.C++ boost给出了更实用的scoped ptr和shared ptr和weak ptr.

scoped ptr 防拷贝-》简单粗暴,对于不需要拷贝的场景非常好

shared ptr 引用计数,最后一个释放的对象释放资源 -》复杂一些,但是支持拷贝,非常完美-》问题:循环引用

weak ptr 解决循环引用,不参与引用计数
3.C++ TR1,引入了shared ptr等。不过注意的是TR1并不是标准版,
4.C++ 11,引入了unique_ptr和shared ptr和weak_ptr。需要注意的是unique_ptr对应boost 的scoped ptr。并且这些智能指针的实现原理是参考boost中的实现的。

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

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

相关文章

谷粒商城学习笔记-19-快速开发-逆向生成所有微服务基本CRUD代码

文章目录 一&#xff0c;使用逆向工程步骤梳理1&#xff0c;修改逆向工程的application.yml配置2&#xff0c;修改逆向工程的generator.properties配置3&#xff0c;以Debug模式启动逆向工程4&#xff0c;使用逆向工程生成代码5&#xff0c;整合生成的代码到对应的模块中 二&am…

VPS拨号服务器:独享的高效与安全

在当今互联网高速发展的时代&#xff0c;虚拟私人服务器&#xff08;VPS&#xff09;已成为许多企业和个人用户托管网站、应用程序的首选。特别是带有拨号功能的VPS服务器&#xff0c;以其独特的优势受到广泛关注。本文将深入探讨VPS拨号服务器的独享特性&#xff0c;以及它如何…

Vue 使用Audio或AudioContext播放本地音频

使用Audio 第一种 使用标签方式 <audio src"./tests.mp3" ref"audio"></audio><el-button click"audioPlay()">播放Audio</el-button>audioPlay() {this.$refs.audio.currentTime 0;this.$refs.audio.play();// this.$…

c++方法

std::transform方法 std::transform 是 C 标准库算法中的一个非常有用的函数&#xff0c;它定义在头文件 中。这个函数用于将给定范围内的每个元素按照指定的操作进行转换&#xff0c;并将转换结果存储在另一个位置&#xff08;可以是原始范围的另一个容器&#xff0c;或者完全…

HarmonyOS应用开发前景及使用工具

HarmonyOS应用开发001 文章目录 前言前景一、技术特性二、使用工具1.项目目录结构 前言 学习之前&#xff0c;需要有一定的开发基础&#xff08;如&#xff1a;java、c#、c、WEB前端的一些了解)。 HarmonyOS开发使用的ArkTS&#xff0c;ArkTS是在TS的基础之上进行封装的&#…

外科休克病人的护理

一、引言 休克是外科常见的危急重症之一,它是由于机体遭受强烈的致病因素侵袭后,有效循环血量锐减、组织灌注不足所引起的以微循环障碍、细胞代谢紊乱和器官功能受损为特征的综合征。对于外科休克病人的护理,至关重要。 二、休克的分类 外科休克主要分为低血容量性休克(包括…

VMware Workstation 虚拟机网络配置为与主机使用同一网络

要将 VMware Workstation 虚拟机网络配置为与主机使用同一网络&#xff0c;我们需要将虚拟机的网络适配器设置为桥接模式。具体步骤如下&#xff1a; 配置 VMware Workstation 虚拟机网络为桥接模式 打开 VMware Workstation&#xff1a; 启动 VMware Workstation。 选择虚拟机…

博客网站目录网址导航自适应主题php源码

开源免费 博客屋网址导航自适应主题php源码v1.0是一款免费开源的PHP分类导航建站程序&#xff0c;源代码公开且无任何加密代码、安全有保障、无后门隐患。 系统稳定 内核安全稳定、PHPMYSQL/Sqlite架构、跨平台运行;版本自带ico接口集成&#xff0c;添加网站时&#xff0c;可自…

PostGIS2.4服务器编译安装

PostGIS的最新版本已经到3.5&#xff0c;但是还有一些国产数据库内核使用的旧版本的PostgreSQL&#xff0c;支持PostGIS2.4。但PostGIS2.4的版本已经在yum中找不到了&#xff0c;安装只能通过本地编译的方式。这里介绍一下如何在Centos7的系统上&#xff0c;编译部署PostGIS2.4…

实验场:在几分钟内使用 Bedrock Anthropic Models 和 Elasticsearch 进行 RAG 实验

作者&#xff1a;来自 Elastic Joe McElroy, Aditya Tripathi 我们最近发布了 Elasticsearch Playground&#xff0c;这是一个新的低代码界面&#xff0c;开发人员可以通过 A/B 测试 LLM、调整提示&#xff08;prompt&#xff09;和分块数据来迭代和构建生产 RAG 应用程序。今天…

Web3学习路线图,从入门到精通

前面我们聊了Web3的知识图谱&#xff0c;内容是相当的翔实&#xff0c;要从哪里入手可以快速的入门Web3&#xff0c;本篇就带你看看Web3的学习路线图&#xff0c;一步一步深入学习Web3。 这张图展示了Web3学习路线图&#xff0c;涵盖了区块链基础知识、开发方向、应用开发等内…

桥接模式案例

桥接模式&#xff08;Bridge Pattern&#xff09;是一种结构型设计模式&#xff0c;它将抽象部分与实现部分分离&#xff0c;使它们可以独立变化。桥接模式通过创 建一个桥接接口&#xff0c;将抽象部分和实现部分连接起来&#xff0c;从而实现两者的解耦。下面是一个详细的桥接…

接上一回C++:补继承漏洞+多态原理(带图详解)

引子&#xff1a;接上一回我们讲了继承的分类与六大默认函数&#xff0c;其实继承中的菱形继承是有一个大坑的&#xff0c;我们也要进入多态的学习了。 注意&#xff1a;我学会了&#xff0c;但是讲述上可能有一些不足&#xff0c;希望大家多多包涵 继承复习&#xff1a; 1&…

windows环境下基于3DSlicer 源代码编译搭建工程开发环境详细操作过程和中间关键错误解决方法说明

说明: 该文档适用于  首次/重新 搭建3D-Slicer工程环境  Clean up(非增量) 编译生成 1. 3D-slicer 软件介绍 (1)3D Slicer为处理MRI\CT等图像数据软件,可以实行基于MRI图像数据的目标分割、标记测量、坐标变换及三维重建等功能,其源于3D slicer 4.13.0-2022-01-19开…

duplicate key value violates unique constraint

duplicate key value violates unique constraint 遇到的问题 你在尝试向数据库表 goods 插入新记录时&#xff0c;收到了 duplicate key value violates unique constraint 的错误。尽管你确认数据库中没有与尝试插入的 id 相同的记录&#xff0c;但错误依旧存在。进一步的调…

OS Copilot测评

1.按照第一步管理重置密码时报错了,搞不懂为啥?本来应该跳转到给的那个实例的,我的没跳过去 2.下一步重置密码的很丝滑没问题 3安全组新增入库22没问题 很方便清晰 4.AccessKey 还能进行预警提示 5.远程连接,网速还是很快,一点没卡,下载很棒 6.替换的时候我没有替换<>括…

【JavaEE】网络编程——UDP

&#x1f921;&#x1f921;&#x1f921;个人主页&#x1f921;&#x1f921;&#x1f921; &#x1f921;&#x1f921;&#x1f921;JavaEE专栏&#x1f921;&#x1f921;&#x1f921; 文章目录 1.数据报套接字(UDP)1.1特点1.2编码1.2.1DatagramSocket1.2.2DatagramPacket…

数据建设实践之数据规范

目录 一、命名规范 1.业务系统简称规范 2.数据源简称规范 3.数仓分层规范 4.业务板块简称规范 4.数据域简称规范 5.数据域简称规范 6.业务过程简称规范 7.表名规范 通用规范&#xff1a; 二、字段规范 1.Hive表字段类型规范 2.CK表字段类型规范 3.字段默认值规范 …

Spring Cloud Alibaba AI 介绍及使用

一、Spring Cloud Alibaba AI 介绍 Spring AI 是 Spring 官方社区项目&#xff0c;旨在简化 Java AI 应用程序开发&#xff0c;让 Java 开发者像使用 Spring 开发普通应用一样开发 AI 应用。而 Spring Cloud Alibaba AI 是阿里以 Spring AI 为基础&#xff0c;并在此基础上提供…

dive deeper into tensor:从底层开始学习tensor

inspired by karpathy/micrograd: A tiny scalar-valued autograd engine and a neural net library on top of it with PyTorch-like API (github.com)and Taking PyTorch for Granted | wh (nrehiew.github.io). 这属于karpathy的karpathy/nn-zero-to-hero: Neural Networks…