首页 > 编程语言 >C++ 智能指针

C++ 智能指针

时间:2024-06-23 11:59:59浏览次数:23  
标签:计数 int C++ Ptr 智能 pcount new ptr 指针

 问题引入

int func1(int x)
{
	int y = 10;
	int* tmp = (int*)malloc(sizeof(int) * 2);
	if (x == 0)
		throw "func1_error";
	else
		return x + y;
	free(tmp);//抛异常造成异常安全问题,无法释放造成内存泄漏,
}

int main()
{
	try { int a=func1(0); }
	catch (const char* error)
	{
		cout << error << endl;
	}
	return 0;
}

内存泄漏:

什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。

内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。

智能指针的使用及原理

RAII(面试官喜欢问)

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

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

这种做法有两大好处:

不需要显式地释放资源。

采用这种方式,对象所需的资源在其生命期内始终保持有效。

核心:通过对象的生命周期管理资源的释放

发展历史

weak_ptr不增加share_ptr的引用计数,但是可以观察这个引用计数,不参与他的修改

使用RAII思想设计的Smart_sptr类


template<class T>
class Smart_sptr
{
public:
	Smart_sptr(T* sptr=nullptr)
		:_ptr(sptr) 
	{}

	~Smart_sptr()
	{ 
		if (_ptr)
		{
			delete _ptr;
			cout << "~Smart_sptr" << endl;
		}
			
	}

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

private:
	T* _ptr;
};

struct Date
{
	int _year;
	int _month;
	int _day;
};


int main()
{
	//实现指针操作
	Smart_sptr<int> sp1(new int);
	*sp1 = 10;
	cout << *sp1 << endl;


	//管理资源
	Smart_sptr<Date> sp2(new Date);
	// 需要注意的是这里应该是sparray.operator->()->_year = 2018;
	// 本来应该是sparray->->_year这里语法上为了可读性,省略了一个->
	sp2->_year = 2018;
	sp2->_month = 1;
	sp2->_day = 1;
	cout << sp2->_year << " " << sp2->_month << " " << sp2->_day << endl;
	
	//测试生命周期
	cout << (*(Smart_sptr<int>(new int)) = 11) << endl;
	//执行完后立马析构,资源释放
	return 0;
}

将新创建的new int对象交给sp1,将new Date对象交给视2

通过类对象去控制new出来的对象属性,

	int* tmp = new int;
	sp1 =tmp

难以理解可以直接将sp1是tmp的一个别名使用,依旧还是指针形式,存的是一块地址

结构体,对象通过 . 访问

结构体对象取地址通过->访问:本质是sp2.operator->()->_day

 std::auto_ptr

C++98版本的库中就提供了auto_ptr的智能指针。

auto_ptr的实现原理:管理权转移的思想

#include <memory>
class Date
{
public:
	Date() { cout << "Date()" << endl; }
	~Date() { cout << "~Date()" << endl; }
	int _year;
	int _month;
	int _day;
};
int main()
{
	auto_ptr<Date> ap(new Date);
	auto_ptr<Date> copy(ap);
	// auto_ptr的问题:当对象拷贝或者赋值后,前面的对象就悬空了copy()括号内的内容悬空
	// C++98中设计的auto_ptr问题是非常明显的,所以实际中很多公司明确规定了不能使用auto_ptr
	ap->_year = 2018;
	return 0;
}

std::unique_ptr

unique_ptr的实现原理:简单粗暴的防拷贝

如何防拷贝参考:防拷贝文章

int main()
{
	unique_ptr<Date> up(new Date);

	// unique_ptr的设计思路非常的粗暴-防拷贝,也就是不让拷贝和赋值。
	unique_ptr<Date> copy(ap);
	return 0;
}

std::shared_ptr

 简单样例

struct Date
{
	int _year;
	int _month;
	int _day;
};

int main()
{
	// shared_ptr通过引用计数支持智能指针对象的拷贝
	shared_ptr<Date> sp(new Date);
	//use_count()返回引用计数个数
	cout << "第一个对象sp管理new Date的引用计数:" << sp.use_count() << endl;

	shared_ptr<Date> copy(sp);
	cout << "新增一个对象后sp的引用计数:" << sp.use_count() << endl;
	cout << "第二个对象copy管理new Date的引用计数:" << copy.use_count() << endl;
	return 0;
}

图解

新申请的资源new Date其内部含有一个数据(引用计数)专门记录有多少对象能访问该资源

当sp对象管理new Date时,sp会让new Date的引用计数增加1

当copy对象管理new Date时,copy会让new Date的引用计数增加1

shared_ptr的原理:

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

1. shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。

2. 在对象被销毁时(也就是析构函数调用),说明自己不使用该资源了,对象的引用计数减1。

3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;

4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。

模拟实现Shared_Ptr

初级版本

#include<string>
template<class T>
class Shared_Ptr
{
public:
	//构造
	Shared_Ptr(T* ptr = nullptr)
		:_ptr(ptr)
		, _pcount(new int(1))//数组,开始初始化赋值为1
	{}

	//析构函数
	~Shared_Ptr()
	{
		//初始版本
		//if (_pcount == 0)
		//{
		//	delete _pcount;
		//	delete _ptr;
		//}
		//else
		//{
		//	--_pcount;
		//}

		//保证赋值重载能调用析构
		release();
	}

	//拷贝构造
	Shared_Ptr(const Shared_Ptr<T>& sp)
		:_ptr(sp._ptr)
		,_pcount(sp._pcount)
	{
		++(*_pcount);//拷贝后多一个对象管理,引用计数+1
	}


	//实现智能指针
	T& operator*() { return *_ptr; }
	T* operator->() { return _ptr; }

	//获得引用计数个数
	int use_count() { return *_pcount; }

	//返回类中原式指针
	T* get() { return _ptr; }

	void release()
	{
		//当引用计数为1时,不再让该指针管理资源,应该立马释放
		
		//如果是下面这样,当引用计数为1时,只让引用计数-1并没有释放
		//正常情况下引用计数为1时在调用release时就应该delete了
		//if (*_pcount != 0)或者改成!=1
		//{
		//	--(*_pcount);
		//}
		//else
		//{
		//	delete _pcount;
		//	delete _ptr;
		//}
		if (--(*_pcount) == 0)
		{
			delete _pcount;
			delete _ptr;
		}
	}

	//赋值重载--遇见想要多个对象管理时sp1=sp2
	Shared_Ptr<T>& operator=(const Shared_Ptr<T>& sp) 
	{
		//两种情况--1.自己赋值,2.两个不同的赋值
		if (sp._ptr!= _ptr)//不同
		{
			//释放sp1所管理的内容or减去sp1所管理内容的引用计数
			//从引用计数上保证sp1不在管理该块内容
			//调用析构,多套一层release函数
			release();
			_ptr = sp._ptr;
			_pcount = sp._pcount;
			++(*_pcount);
		}
		//else
		//{
		//	return *this;
		//	//相同返回对象本身
		//}
		全部完成后返回对象本身
		//return *this;
		//上述两个return可以合并成一个,在执行完不同对象的赋值后需要返回*this
		//相同时不进入if也返回*this
		return *this;
	}

private:
	int* _pcount;//引用计数个数
	T* _ptr;//管理指针
};

int main()
{
	Shared_Ptr<string> sp1(new string("test_smart_pointer"));
	Shared_Ptr<string> sp2(sp1);//测试拷贝
	cout << "sp1:" << sp1.get() << " _pcount " << sp1.use_count() << endl;//use_count()显示总引用计数
	cout << "sp2:" << sp2.get() << " _pcount " << sp2.use_count() << endl;
	cout << endl;
	Shared_Ptr<string> sp3(new string("test_assignment"));
	sp3 = sp3;//相同赋值
	cout << "sp3:" << sp3.get() << " _pcount " << sp3.use_count() << endl;
	sp1 = sp3;//不同赋值
	cout << "sp1:" << sp1.get() << " _pcount " << sp1.use_count() << endl;
	cout << "sp2:" << sp2.get() << " _pcount " << sp2.use_count() << endl;
	cout << "sp3:" << sp3.get() << " _pcount " << sp3.use_count() << endl;

	return 0;
}

结果:

开始时new string("test_smart_pointer")的地址为00B6F410,由智能指针sp1管理,又经过拷贝后sp2也能管理00B6F410,此时有两个智能指针管理该00B6F410地址所存资源,所以每一个智能指针的引用计数均为2

new string("test_assignment")创造的资源地址为:00B6F1D0,由智能指针sp3管理,引用计数为1,并且自我赋值后引用计数依旧为1。

因为是sp3所管理的资源赋值给sp1,所以sp1不在管理00B6F410而是管理00B6F1D0,并且此时sp1和sp3均管理00B6F1D0所以引用计数为2

此时管理00B6F410的只有sp2,引用计数为1

再加入测试代码检测sp2无管理内容时,引用计数个数

	sp2.release();
	cout << "sp2:" << sp2.get() << " _pcount " << sp2.use_count()

此时引用计数为0

升级

新增接收任意删除器

解决不是new出来的对象通过智能指针管理

shared_ptr设计了一个删除器来解决这个问题

原代码测试

// 仿函数的删除器
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((int*)malloc(4), deleteArrayFunc);

	return 0;
}

改造模拟实现代码

针对有删除器的构造函数:


	//构造--删除器函数的接收

	// 方法一://对重载函数的调用不明确
	//Shared_Ptr(T* ptr = nullptr, function<void(T*)> del= [](T* ptr) {delete ptr; })

	//方法二:
	template<class D>
	Shared_Ptr(T* ptr , D del)
		:_ptr(ptr)
		, _pcount(new int(1))
		, _del(del)
	{
	}

	方法三:
	//Shared_Ptr(T* ptr, function<void(T*)> del)
	//	:_ptr(ptr)
	//	, _pcount(new int(1))
	//	, _del(del)
	//{
	//}

方法三提供拓展思路,使用方法二

release函数的重写

	void release()
	{
		//当引用计数为1时,不再让该指针管理资源,应该立马释放
		
		//如果是下面这样,当引用计数为1时,只让引用计数-1并没有释放
		//正常情况下引用计数为1时在调用release时就应该delete了
		//if (*_pcount != 0)或者改成!=1
		//{
		//	--(*_pcount);
		//}
		//else
		//{
		//	delete _pcount;
		//	delete _ptr;
		//}

		if (--(*_pcount) == 0)
		{
			if(_del)//仅验证function对象_del有true or false的属性
			{
				cout << "_del有可调用内容" << endl;
			}
			delete _pcount;
			//delete _ptr;
			_del(_ptr);
		}

	}

注意当引用计数为1时是按0判断还是按照1判断

引用计数为1时释放,说明此时只有一个智能指针管理该资源,--后立马能释放

难以理解就将0换成1,如下

		if ((*_pcount)-- == 1)
		{
			if (_del)//仅验证function对象_del有true or false的属性
			{
				cout << "_del有可调用内容" << endl;
			}
			delete _pcount;
			//delete _ptr;
			_del(_ptr);
		}
		

提供的新的成员变量

private:
	int* _pcount;//引用计数个数
	T* _ptr;//管理指针
	//用包装器接收删除函数,不需要返回值,操作T* _ptr
	//写在成员函数上,默认_del被初始化成[](T* ptr) {delete ptr; },函数内部可以调用_del进行释放
	function<void(T*)> _del = [](T* ptr) {delete ptr; };
};

function<void(T*)> _del = [](T* ptr) {delete ptr; };的包装器的使用值得反复研究

删除器内容

template<class T>
struct DelArray
{
	void operator()(T* ptr)
	{
		delete[] ptr;
	}
};

struct ListNode
{
	int val;
	weak_ptr<ListNode> next;
	weak_ptr<ListNode> prev;

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

测试删除器

	Shared_Ptr<ListNode> sp4(new ListNode[10], DelArray<ListNode>());
	Shared_Ptr<ListNode> sp5(new ListNode[10], [](ListNode* ptr) {delete[] ptr; });
	Shared_Ptr<FILE> sp6(fopen("Test.cpp", "r"), [](FILE* ptr) {fclose(ptr); });

	    if (--(*_pcount) == 0)
		{
			if(_del)
			{			
				_del(_ptr);
				cout << "_del有可调用内容" << endl;
			}
			else
			{
				delete _ptr;
				cout << "_del无可调用内容" << endl;
			}
			delete _pcount;
		}

该段代码仅提供思路参考,面对function对象有无可调用对象的参考

 重载调用不明确

方法一://造成重载调用不明确

方法一在使用Shared_Ptr传递一个T*类型的对象时,

Shared_Ptr(T* ptr = nullptr)

Shared_Ptr(T* ptr = nullptr, function<void(T*)> del= [](T* ptr) {delete ptr; })

这两个构造函数是都能接收的,构造二在接收时默认有一个缺省值,构造一二都会调用


情况1:void fun(int a = 1) { cout << a << endl; }

情况2:void fun(int a = 1, int b = 2) { cout << a << " " << b << endl; }

int main(){
	//fun(1);//报错,对重载函数的调用不明确
	return 0;
}

此时fun(1)能调用情况1的fun,也能调用情况2的fun,调用1时,a接收内容。调用2时,a接收内容,b使用缺省值。这两种情况都是能被调用的,造成调用不明确

使调用重载明确方法:

//方法1:这样的修改使得构造函数的重载更加明确,因为现在有两个不同的参数类型
template<class D>
void fun(int a , D b=1 ) { cout << a << " " << b << endl; }
//方法2:
void fun(int a, int b) { cout << a << " " << b << endl; }

这里class D为了让两个函数重载的更加明显,避免编译器又分不清

引入D后,迫使fun函数调用时必须给第二个参数传参,才能调用void fun(int a , D b=1 ),避免因固定类型的缺省导致重载调用不明确

缺陷

Shared_Ptr<ListNode> sp4(new ListNode[10], DelArray<ListNode>());
Shared_Ptr<ListNode> sp5(new ListNode[10], [](ListNode* ptr) {delete[] ptr; });

sp4->next=sp5;
sp5->prev=sp4;

造成循环引用

sp4->next=sp5;
sp5->prev=sp4;

使用时应避免出现相互交叉的情况

形成闭合回路的情况会造成  循环引用

 

标签:计数,int,C++,Ptr,智能,pcount,new,ptr,指针
From: https://blog.csdn.net/weixin_62551043/article/details/139836299

相关文章

  • 大数据主流技术演进历程:从传统数据处理到智能数据分析
    大数据技术的发展历程充满了创新和变革。从最初的批处理系统到如今的实时数据分析平台,技术的演进不仅推动了数据处理能力的提升,也改变了各行各业的运营模式。本文将深入探讨大数据主流技术的演进历程,分析其技术亮点、实际应用以及对行业的深远影响。一、传统数据处理阶段......
  • C++入门 vector深度剖析及模拟实现
    目录vector析构函数模拟实现vector赋值拷贝模拟实现vector拷贝构造模拟实现vector构造函数模拟实现类模板的成员函数n个val构造单参数和多参数对象隐式类型转换使用memcpy拷贝问题在上两篇有关vector的模拟实现中,还有构造,拷贝构造,赋值拷贝以及析构函数没有实现,本篇主......
  • 【昆虫识别系统】图像识别Python+卷积神经网络算法+人工智能+深度学习+机器学习+Tenso
    一、介绍昆虫识别系统,使用Python作为主要开发语言。通过TensorFlow搭建ResNet50卷积神经网络算法(CNN)模型。通过对10种常见的昆虫图片数据集('蜜蜂','甲虫','蝴蝶','蝉','蜻蜓','蚱蜢','蛾','蝎子','蜗牛','蜘蛛')进行训练,得到一个识别精度较......
  • C++ 20新特性之改进的位操作
    ......
  • 开发查询订单信息fastGPT智能体工作流 将工作流接入到人工客服系统
    我在抖音上发布了视频https://www.douyin.com/video/7382446337482099977下面是主要内容介绍【视频标题:】开发查询订单信息fastGPT智能体工作流将工作流接入到人工客服系统#智能体#FastGPT#客服系统-----------【视频行业分类:】<3C数码>-----------【视频文案】:我们使用fa......
  • 【小沐学GIS】Google的kml文件的读写(C++、Python)
    文章目录1、简介1.1kml简介1.2功能点1.2.1地标1.2.2地面叠加层1.2.3路径1.2.4多边形2、下载和编译3、C++测试4、Python测试4.1安装库4.2测试14.2测试24.3测试3结语1、简介https://developers.google.cn/kml/documentation/kmzarchives?hl=zh-cn1.1kml......
  • 一、若依--P2--P5【黑马程序员Java最新AI+若依框架项目开发新方案视频教程,基于RuoYi-V
    学习视频【黑马程序员Java最新AI+若依框架项目开发新方案视频教程,基于RuoYi-Vue3前后端分离版本,从前端到后端再到AI智能化应用全通关】https://www.bilibili.com/video/BV1pf421B71v/?p=6&share_source=copy_web&vd_source=3949d51b57b2891ea14d6e51c792bef6P2:前端框架搭......
  • C++ 结构体对齐详解
    目录前言一、为什么要对结构体进行对齐操作?二、基本概念三、对齐规则四、示例讲解1.简单的变量对齐2.结构体包含有结构体的对齐结构体成员详细解析五、使用指令改变对齐方式__attribute__((packed))#pragmapack(push,n)#pragmapack(pop)六、总结前......
  • 2024年华为OD机试真题-生成哈夫曼树-(C++/Java/python)-OD统一考试(C卷D卷)
    题目描述给定长度为n的无序的数字数组,每个数字代表二叉树的叶子节点的权值,数字数组的值均大于等于1。请完成一个函数,根据输入的数字数组,生成哈夫曼树,并将哈夫曼树按照中序遍历输出。为了保证输出的二叉树中序遍历结果统一,增加以下限制:二叉树节点中,左节点权值小于右节点......
  • 【C++进阶学习】第三弹——菱形继承和虚拟继承——菱形继承的二义性和数据冗余问题
    继承(上):【C++进阶学习】第一弹——继承(上)——探索代码复用的乐趣-CSDN博客继承(下):【C++进阶学习】第二弹——继承(下)——挖掘继承深处的奥秘-CSDN博客前言:在前面,我们已经讲过继承的相关知识,今天我们来将一个由继承拓展出来的很重要的知识,那就是——菱形继承和虚拟继承及相关知......