[C++11] 智能指针

news2024/11/26 10:20:22

         长路漫漫,唯剑作伴。        

目录

         长路漫漫,唯剑作伴。        

为什么需要智能指针

RAII

使用RAII思想管理内存

重载 * 和->

总结一下智能指针的原理:

C++的智能指针和拷贝问题

auto_ptr        (C++98)   ​编辑

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

unique_ptr        (C++11)

std::shared_ptr        (C++11)

        sheared_ptr 的简化实现

        关于循环引用的问题(Circular reference)

weak_ptr        打破循环引用(C++11)

        weak_ptr 的简化实现

定制删除器

定制删除器的使用

unique_ptr 定制删除器的模拟实现

相关链接 :


为什么需要智能指针

int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");

	return a / b;
}

void func()
{
	int* p1 = new int[10]; // 这里亦可能会抛异常

	int* p2 = new int[10]; // 这里亦可能会抛异常
	int* p3 = new int[10]; // 这里亦可能会抛异常
	int* p4 = new int[10]; // 这里亦可能会抛异常

	try
	{
		div();
	}
	catch (...)
	{
		delete[] p1;
		delete[] p2;
		delete[] p3;
		delete[] p4;

		throw;
	}

	delete[] p1;
	delete[] p2;
	delete[] p3;
	delete[] p4;
}

int main()
{
	try
	{
		func();
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
		// ...
	}

	return 0;

        可以发现上面这段代码是典型的异常处理中可能会碰到内存泄漏或析构异常的例子,我们当然可以为每个new[]都尝试着抛出异常,但那样太麻烦了,所以我们引入了智能指针

RAII

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

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

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

使用RAII思想管理内存

// 使用RAII思想设计的SmartPtr类

template<class T>
class SmartPtr{
public:
    SmartPtr(T* ptr = nullptr)
        : _ptr(ptr){
        cout << "get " << _ptr << endl;

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

private:
    T* _ptr;
};

void Func(){
    int* pa = new int(10);
    cout << " pa " << pa << endl;
    SmartPtr<int> sp1(pa);

}
int main(){
    Func();
    return 0;
}

重载 * 和->

        上述的SmartPtr还不能将其称为智能指针,因为它还不具有指针的行为。指针可以解引用,也可 以通过->去访问所指空间中的内容,因此:SmartPtr模板类中还得需要将* 、->重载下,才可让其像指针一样去使用。

#include<iostream>
#include<memory>
using namespace std;

//使用RAII 思想管理内存


template<class T>
class SmartPtr{
public:
    SmartPtr(T* ptr = nullptr)
        : _ptr(ptr){
        //cout << "get " << _ptr << endl;

    }
    ~SmartPtr(){
        if (_ptr)
            //cout << "delet " <<_ptr<< endl;
            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> spDate(new Date);
    // 需要注意的是这里应该是spDate.operator->()->_year = 2018;
    // 本来应该是spDate->->_year这里语法上为了可读性,省略了一个->
    spDate->_year = 2060;
    spDate->_month = 6;
    spDate->_day = 14;

    cout << spDate->_year << "/" << spDate->_month << "/" << spDate->_day << endl;
    return 0;
}

        但是尽管如此,我们仍旧面临拷贝问题和析构问题,这里先抛出问题,文章后面会解释C++智能指针是如何解决这类的问题的,一步一步来,我们会解决拷贝问题,然后再解决析构问题(定制删除器

void copy_issue(){
    SmartPtr<int> sp1(new int);
    SmartPtr<int> sp2 = sp1;        //这里会析构2次而导致程序异常终止
}


void destructor_issue(){
    SmartPtr<int> sp1(new int[10]);  //这里的析构存在类型不匹配,没办法正确的调用 delete[],
                                    //类似的还有文件描述符,也没办法正确 close()...
}

总结一下智能指针的原理:

  • RAII特性
  • 重载operator*和opertaor->,具有像指针一样的行为
  • 处理拷贝问题
  • 定制删除器

C++的智能指针和拷贝问题

auto_ptr        (C++98)   

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

        下面简化模拟实现了一份skate::auto_ptr来了解它的原理:

namespace skate{
	template<class T>
	class auto_ptr{
	public:
		auto_ptr(T* ptr)
			:_ptr(ptr){
		}

		auto_ptr(auto_ptr<T>& sp)   //拷贝构造
			:_ptr(sp._ptr){
			// 管理权转移
			sp._ptr = nullptr;
		}

		auto_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;
	};
}

//结论:auto_ptr是一个失败设计,很多公司明确要求不能使用auto_ptr
int main(){
	skate::auto_ptr<int> sp1(new int);
	*sp1 = 10;

	skate::auto_ptr<int> sp2(sp1); // 管理权转移

	   //sp1悬空
	*sp1 = 20;			//空指针异常
	*sp2 = 30;
	cout << *sp2 << endl;
	cout << *sp1 << endl;
	return 0;
}

unique_ptr        (C++11)

C++11中开始提供更靠谱的unique_ptr 


unique_ptr的实现原理:简单粗暴的防拷贝,下面简化模拟实现了一份UniquePtr来了解它的原理

// C++11库才更新智能指针实现
// C++11出来之前,boost搞除了更好用的scoped_ptr/shared_ptr/weak_ptr
// C++11将boost库中智能指针精华部分吸收了过来
// C++11->unique_ptr/shared_ptr/weak_ptr

// unique_ptr/scoped_ptr
// 原理:简单粗暴 -- 防拷贝
namespace skate{
	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;
	};
}

std::shared_ptr        (C++11)

C++11中开始提供更靠谱的并且支持拷贝shared_ptr 
shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。

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

class MyClass{
public:
	MyClass(){
		std::cout << "MyClass constructor called" << std::endl;
	}

	~MyClass(){
		std::cout << "MyClass destructor called" << std::endl;
	}
};

int main(){

	std::shared_ptr<MyClass> ptr1(new MyClass);  //调用构造函数
	std::cout << ptr1.get() << " _ptr-> ptr1.use_count() = " << ptr1.use_count() << std::endl;
	std::shared_ptr<MyClass> ptr2 = ptr1;//调用赋值重载    计数++
	std::cout << ptr2.get() << " _ptr-> ptr2.use_count() = " << ptr2.use_count() << std::endl;
	std::shared_ptr<MyClass> ptr3(ptr1);//调用拷贝构造  计数++
	std::cout << ptr3.get() << " _ptr-> ptr3.use_count() = " << ptr3.use_count() << std::endl;

	ptr1.reset(); //计数--

	ptr2.reset();//计数--

	ptr3.reset();//计数为0  调用析构


	std::cout << ptr3.get() << " _ptr-> ptr3.use_count() = " << ptr3.use_count() << std::endl;

	return 0;
}

 

sheared_ptr 的简化实现

	template<class T>
	class shared_ptr
	{
	public:
		void Release()// 释放指向的对象和引用计数
		{
			if (--(*_pCount) == 0 && _ptr) //计数为0进行析构
			{
				cout << "delete" << _ptr << endl;
				delete _ptr;
				_ptr = nullptr;

				delete _pCount;
				_pCount = nullptr;
			}
		}

		// RAII思想
		shared_ptr(T* ptr)
			:_ptr(ptr)
			, _pCount(new int(1))
		{}

		~shared_ptr()
		{
			Release();//析构先时候 --计数,后确认析构
		}

		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			, _pCount(sp._pCount)
		{
			(*_pCount)++;
		}

		// sp1 = sp3
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			//if (this != &sp)
			if (_ptr != sp._ptr)
			{
				Release();//先 --计数,后确认析构

				_ptr = sp._ptr;
				_pCount = sp._pCount;
				++(*_pCount);
			}

			return *this;
		}


		// 像指针一样
		T& operator*()
		{
			return *_ptr;
		}

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

		T* get() const
		{
			return _ptr;
		}

		int use_count()
		{
			return *_pCount;
		}

	private:
		T* _ptr;
		int* _pCount;
	};

        需要注意的是,这只是一份简单的shared_ptr模拟实现,并没有考虑线程安全等方面的问题。实际上,C++11中的标准库中的shared_ptr实现更加复杂和完善,具有线程安全等特性。

关于循环引用的问题(Circular reference)

        循环引用是指两个或多个智能指针对象直接或间接地引用彼此,形成一个引用链,导致资源无法正确释放。当涉及的对象之间的引用计数永远不会降为0时,就会发生循环引用问题。

        这种情况下,我们可以使用weak_ptr来打破循环引用,从而避免内存泄漏的问题。

 循环引用分析: 

  1.  node1和node2两个智能指针对象指向两个节点,引用计数变成1,我们不需要手动delete。
  2. node1的_next指向node2,node2的_prev指向node1,引用计数变成2。
  3. node1和node2析构,引用计数减到1,但是_next还指向下一个节点。但是_prev还指向上一个节点。
  4. 也就是说_next析构了,node2就释放了。
  5. 也就是说_prev析构了,node1就释放了。
  6. 但是_next属于node的成员,node1释放了,_next才会析构,而node1由_prev管理,_prev属于node2成员,所以这就叫循环引用,谁也不会释放。

weak_ptr        打破循环引用(C++11)

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)
		{
			if (_ptr != sp.get())
			{
				_ptr = sp.get();
			}

			return *this;
		}

		// 像指针一样
		T& operator*()
		{
			return *_ptr;
		}

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

	public:
		T* _ptr;
	};
}

定制删除器

定制删除器的使用

class Date{
public:
	~Date(){
		cout << "~Date()" << endl;
	}
private:
	int _year = 1;
	int _month = 1;
	int _day = 1;
};

// unique_ptr/shared_ptr  默认释放资源用的delete
// 如何匹配申请方式去对应释放呢?

template<class T>
struct DeleteArray{
	void operator()(T* ptr){
		cout << "delete[]" << ptr << endl;
		delete[] ptr;
	}
};

template<class T>
struct Free{
	void operator()(T* ptr){
		cout << "free" << ptr << endl;
		free(ptr);
	}
};

struct Fclose{
	void operator()(FILE* ptr){
		cout << "fclose" << ptr << endl;
		fclose(ptr);
	}
};
void test_unique(){
	cout << "\ntest_unique()\n";
	// unique_ptr传类型
	std::unique_ptr<Date> up1(new Date);
	std::unique_ptr<Date, DeleteArray<Date>> up2(new Date[5]);
	std::unique_ptr<Date, Free<Date>> up3((Date*)malloc(sizeof(Date) * 5));
	std::unique_ptr<FILE, Fclose> up4((FILE*)fopen("Test.cpp", "r"));
}
void test_shared(){

	cout << "\ntest_shared()\n";
	// shared_ptr传对象
	std::shared_ptr<Date> sp1(new Date[5], DeleteArray<Date>());
	std::shared_ptr<Date> sp2(new Date[5], [](Date* ptr){cout << "lambda delete[] " << ptr << endl;; delete[] ptr; });
}
//定制删除器

int main(){
	test_unique();
	test_shared();
	return 0;
}

unique_ptr 定制删除器的模拟实现

ps:

C++11中的标准库中的shared_ptr实现更加复杂和完善,具有线程安全等特性。封装的也更加彻底,这里就不写模拟实现了

相关链接 :

https://cplusplus.com/reference/memory/auto_ptr/

https://cplusplus.com/reference/memory/unique_ptr/

https://cplusplus.com/reference/memory/shared_ptr/

https://cplusplus.com/reference/memory/weak_ptr/

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

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

相关文章

EmGUCV中类函数 FastFeatureDetector使用详解

FastFeatureDetector Class 释义&#xff1a;FAST&#xff08;加速检测特&#xff09;关键点检测器&#xff0c;源自 E. Rosten ("Machine learning for high-speed corner detection, 2006). 继承关系&#xff1a;Emgu.CV.Features2D.FastFeatureDetector 派生&#xff…

记录好项目D5

记录好项目 你好呀&#xff0c;这里是我专门记录一下从某些地方收集起来的项目&#xff0c;对项目修改&#xff0c;进行添砖加瓦&#xff0c;变成自己的闪亮项目。修修补补也可以成为毕设哦 本次的项目是 商品信息管理系统 技术栈&#xff1a;SpringBoot Mybatis Thymelea…

MATLAB|主动噪声和振动控制算法——对较大的次级路径变化具有鲁棒性

\ &#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭…

细谈容器化技术实现原理--以Docker为例

目录 一、Docker解决了什么 二、容器的发展过程 三、容器基础 3.1. 容器实现的原理&#xff1a; ⚠️原理详解&#xff1a; 3.1.1. Namespace 3.1.2. Cgroups 3.1.3. chroot 四、Volume 4.1. Docker是如何做到把一个宿主机上的目录或者文件&#xff0c;挂载到容器里面…

4. 数组更新检测

4.1 v-for更新监测 当v-for遍历的目标结构改变, Vue触发v-for的更新 情况1: 数组翻转 情况2: 数组截取 情况3: 更新值 口诀: 数组变更方法, 就会导致v-for更新, 页面更新 数组非变更方法, 返回新数组, 就不会导致v-for更新, 可采用覆盖数组或this.$set() 这些方法会触发数组改…

基于深度学习的高精度鸽子检测识别系统(PyTorch+Pyside6+YOLOv5模型)

摘要&#xff1a;基于深度学习的高精度鸽子检测识别系统可用于日常生活中或野外来检测与定位鸽子目标&#xff0c;利用深度学习算法可实现图片、视频、摄像头等方式的鸽子目标检测识别&#xff0c;另外支持结果可视化与图片或视频检测结果的导出。本系统采用YOLOv5目标检测模型…

XSS注入——反射性XSS

xss注入的攻击步骤&#xff1a; 1.查找可能存在的注入点&#xff08;搜索框&#xff0c;留言板&#xff0c;注册&#xff09; 2.观察输出显示位置&#xff1a; html&#xff1a; 尖括号外部&#xff0c; 尖括号内部: 引号内部》闭合&#xff0…

Django | 基于pycharm的django配置52张全流程截图,红星给你一步一步的男妈妈式教学

演示版本&#xff1a;【windows系统】python3.10pycharm2023.1.2django4.2.2 &#xff08;本教程全程在虚拟机中演示&#xff0c;读者无需配置虚拟机&#xff0c;直接按教程安装即可&#xff09; 目录 1.搞到必要的安装包 2.事先准备 3.安装chrome浏览器&#xff08;也可以…

国产MCU-CW32F030开发学习--按键检测

国产MCU-CW32F030开发学习–按键检测 bsp_key 按键驱动程序用于扫描独立按键&#xff0c;具有软件滤波机制&#xff0c;采用 FIFO 机制保存键值。可以检测 如下事件&#xff1a; 按键按下。 按键弹起。 长按键。 长按时自动连发。 我们将按键驱动分为两个部分来介绍&#xff…

C语言学习笔记:顺序结构

✨博文作者&#xff1a;烟雨孤舟 &#x1f496; 喜欢的可以 点赞 收藏 关注哦~~ ✍️ 作者简介: 一个热爱大数据的学习者 ✍️ 笔记简介&#xff1a;作为大数据爱好者&#xff0c;以下是个人总结的学习笔记&#xff0c;如有错误&#xff0c;请多多指教&#xff01; 目录 程序与…

《面试1v1》Spring基础

&#x1f345; 作者简介&#xff1a;王哥&#xff0c;CSDN2022博客总榜Top100&#x1f3c6;、博客专家&#x1f4aa; &#x1f345; 技术交流&#xff1a;定期更新Java硬核干货&#xff0c;不定期送书活动 &#x1f345; 王哥多年工作总结&#xff1a;Java学习路线总结&#xf…

浅谈微前端

本文呢是我梳理的一个扫盲文&#xff0c;由于最近团队准备使用微前端对项目进行改造&#xff0c;所以我呢就先浅了解一下&#xff1a; 微前端到底是什么&#xff1f; 为什么要使用微前端&#xff1f; 都有哪些微前端方案&#xff1f; 微前端有什么不好的地方吗&#xff1f; 通过…

48 最佳实践-性能最佳实践-Guest-Idle-Haltpoll

文章目录 48 最佳实践-性能最佳实践-Guest-Idle-Haltpoll48.1 概述48.2 操作指导 48 最佳实践-性能最佳实践-Guest-Idle-Haltpoll 48.1 概述 为了保证公平性及降低功耗&#xff0c;当虚拟机vCPU空闲时&#xff0c;虚拟机将执行WFx/HLT指令退出到宿主机中&#xff0c;并触发上…

计算机视觉 - 基于黄金模板比较技术的缺陷检测

一、黄金模板比较概述 基于黄金模板比对的检测是一种常见的视觉应用。当进行缺陷检查而其他缺陷检测方法是不可行的时候,使用金模板比较。另外当物体的表面或物体的形状非常复杂时,此技术特别有用。 虽然说黄金模板比较的技术的思路很简单,但是真正落地实施确不是一件十分容…

广告数仓:数仓搭建(二)

系列文章目录 广告数仓&#xff1a;采集通道创建 广告数仓&#xff1a;数仓搭建 广告数仓&#xff1a;数仓搭建(二) 文章目录 系列文章目录前言DWD层创建1.建表广告事件事实表 2.数据装载初步解析日志解析IP和UA标注无效流量编写脚本 总结 前言 这次我们完成数仓剩下的内容 D…

Web服务器群集:Web基础与HTTP协议

目录 一、理论 1.Web基础 2.HTTP协议 二、实验 1.浏览本地HTML页面 三、总结 一、理论 1.Web基础 &#xff08;1&#xff09;域名和DNS ① 域名 网络是基于TCP/IP 协议进行通信和连接的&#xff0c;每一台主机都有一个唯一的标识&#xff08;固定的IP地 址&#xff0…

【Java面试】什么是SpringMVC?它的工作流程是什么样子的?

文章目录 什么是MVC&#xff1f;MVC组件组件前端控制器DispatcherServlet处理器映射器HandlerMapping处理器适配器HandlAdapter视图解析器ViewResolver处理器Handler视图View 工作原理具体执行流程 什么是MVC&#xff1f; M&#xff1a;model&#xff0c;模型层&#xff0c;包…

搅拌机打蒜机不转维修

打蒜机不转维修&#xff1a;打蒜机用的18650电池&#xff0c;霍尔传感器&#xff0c;Dp0206场效应管。故障为按一下开关显示红灯&#xff1a;电池电压低&#xff01;按下启动按钮电机动一下就再不动了。如果给电池两边加一个5伏电源&#xff0c;打蒜机电机运行正常。那么我把充…

【028】C++ 类和对象的 构造函数、析构函数、拷贝构造、初始化列表 详解(最全讲解)

C类和对象的构造函数、析构函数、拷贝构造、初始化列表详解 引言一、构造函数1.1、数据初始化和清理1.2、构造函数概述1.3、构造函数的定义1.4、提供构造函数的影响 二、析构函数三、拷贝构造函数3.1、拷贝构造的定义3.2、拷贝构造、无参构造、有参构造 三者的关系3.3、拷贝构造…

【群智能算法改进】一种改进的浣熊优化算法 改进长鼻浣熊优化算法 改进后的ICOA[1]算法【Matlab代码#41】

文章目录 【获取资源请见文章第5节&#xff1a;资源获取】1. 原始COA算法1.1 开发阶段1.2 探索阶段 2. 改进后的ICOA算法2.1 Circle映射种群初始化2.2 Levy飞行策略2.3 透镜成像折射反向学习策略 3. 部分代码展示4. 仿真结果展示5. 资源获取 【获取资源请见文章第5节&#xff1…