【C++11】智能指针的定义 和 种类 及 使用

news2024/11/29 0:52:02

智能指针

定义

为什么需要智能指针

在C++中,动态分配内存是一项常见的任务,但手动管理分配和释放内存可能会导致很多问题,如内存泄漏、悬垂指针以及多次释放同一块内存等。为了避免这些问题,引入了智能指针的概念,它们提供了自动化的内存管理。

以下是智能指针可以解决的一些问题
在这里插入图片描述


智能指针的使用 及 原理

智能指针的原理

RAII

RAII

RAII(Resource Acquisition Is Initialization)是一种编程技术和设计原则,它通过将资源的获取与对象的初始化绑定在一起来管理资源。在使用 RAII 时,资源的获取和释放操作被封装在对象的构造函数和析构函数中,利用了对象的生命周期管理资源的自动分配和释放。

该技术的 基本思想

  • 对象的构造函数负责获取资源(如内存、文件句柄、数据库连接等)
  • 析构函数负责释放这些资源。
  • 通过使用 RAII,可以确保在对象离开作用域时,无论是正常退出还是异常退出,资源都会被正确释放,从而避免了资源泄漏的问题

下面利用 RAII 思想实现 一个SmartPtr 的代码:

// 利用RAII思想实现的SmartPtr类
template<class T>
class SmartPtr
{
public:
	// 构造函数 - 获取资源
	SmartPtr(T* ptr = nullptr)
		:_ptr(ptr)
	{}

	// 析构函数 - 释放资源
	~SmartPtr()
	{
		if (_ptr)
		{
			cout << "Delete:" << _ptr << endl;
		}
	}

	// 实现自定义指针类需要的函数
	// 重载 operator* 函数用于实现指针解引用操作,允许通过对象的指针访问该指针所指向的对象。返回类型为 T&,表示对指向 T 类型对象的引用。
	// 重载 operator-> 函数用于实现指针的箭头操作,允许通过对象的指针直接调用该指针所指向对象的成员函数或成员变量。返回类型为 T* ,表示指向 T 类型对象的指针。
	T& operator*()
	{
		return *_ptr;
	}

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

上述代码实现了简要的智能指针的功能,智能指针的原理即

  1. RAII特性
  2. 重载operator*opertaor-> 具有和指针一样的行为

智能指针的种类


auto_ptr

auto_ptrC++98标准中提供的智能指针,它具有独占性质,意味着同一时间只能有一个auto_ptr拥有对特定对象的所有权。

当一个auto_ptr被赋值给另一个auto_ptr时,所有权会被转移,原来的auto_ptr将不再拥有该对象的所有权 (所有权转移) 。这种特性可以用于简单的资源管理,但也容易导致潜在的问题。

由于 auto_ptr 的所有权转移特性,在某些情况下可能会导致意外的行为
例如:

  • 如果将auto_ptr存储在标准容器中,容器的复制或赋值操作会导致对象所有权的转移,从而使得容器内的指针失效。

此外:

  • auto_ptr在异常处理机制方面也存在问题,如果在析构过程中抛出异常,可能会导致资源泄漏。

代码分析

下面对 auto_ptr 的模拟实现,展示了 auto_ptr 的性质

namespace aiyimu
{
	// C++98: auto_ptr 有一定缺陷
	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 operator=(auto_ptr<T>& ap)
		{
			// 给自己赋值不执行操作
			if (this != &ap)
			{
				if (_ptr) //如果_ptr指向了对象,则删除其指向
				{
					cout << "Delete:" << _ptr <<endl;
					delete _ptr;
				}

				_ptr = ap._ptr;
				ap._ptr = nullptr;
			}

			return *this;
		}

	private:
		T* _ptr;
	};
}

通过下面对 std::auto_ptr 的调用可以看出其缺点

void Test_auto_ptr()
{
	std::auto_ptr<int> sp1(new int);
	std::auto_ptr<int> sp2(sp1); //此时所有权转移

	// 此时的 sp1 悬空
	*sp2 = 20;
	cout << *sp2 << endl;
	cout << *sp1 << endl;
}

该函数在调用执行后会报错,原因如下:

  1. sp2通过拷贝构造函数从sp1获取了所有权。这导致原来的sp1变为悬空指针,指向的内存区域不再有效。
  2. 在对悬空指针sp1进行解引用操作时,会导致未定义行为。因此,打印*sp1的语句会产生不可预测的结果。
  3. 由于std::auto_ptr的问题,该代码没有正确处理资源所有权的转移和管理。

综上所述,建议使用C++11标准中提供的智能指针类型,如std::unique_ptr、std::shared_ptr或std::weak_ptr,以避免这些问题,并更好地管理资源所有权和避免悬空指针的情况。


unique_ptr

unique_ptr 的性质:

  1. 拥有性unique_ptr 是一个独占所有权的智能指针,它禁止两个 unique_ptr 对象指向同一个对象
  2. 所有权转移:与 auto_ptr 一样,unique_ptr 支持所有权的转移。通过移动语义,可以将一个 unique_ptr 的所有权从一个对象转移到另一个对象,从而避免了资源的复制和多次删除。
  3. 自动释放:当 unique_ptr 被销毁或者重新赋值时,它会自动删除所拥有的资源避免了内存泄漏
  4. 零开销unique_ptr 本身非常轻量,不引入额外的开销,且常被优化为和裸指针一样的大小和性能。

代码分析原理

通过下面的模拟实现 理解其 原理:

namespace
{
	// unique_ptr 是一个独占所有权的智能指针,它禁止两个 unique_ptr 对象指向同一个对象。
	// 当尝试使用拷贝构造函数或赋值重载运算符来创建或赋值 unique_ptr 对象时,编译器会报错。
	template<class T>
	class unique_ptr
	{
	public:
		// 使用 delete 关键字 禁用其拷贝和赋值
		unique_ptr(unique_ptr<T>& ap) = delete;
		unique_ptr<T>& operator=(unique_ptr<T>& ap) = delete;

		// 构造
		unique_ptr(T* ptr = nullptr)
			: _ptr(ptr)
		{}

		// 析构
		~unique_ptr()
		{
			if (_ptr)
			{
				cout << "Delete" << _ptr << endl;
				delete _ptr;
				_ptr = nullptr;
			}
		}
		
		T& operator*()
		{
			return *_ptr;
		}

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

	private: 
		T* _ptr;
	};
};

下面是一段 使用代码:

class A
{
public:
	~A()
	{
		cout << "~A()" << endl;
	}
	//private:
	int _a1 = 0;
	int _a2 = 0;
};

void test_unique_ptr()
{
	aiyimu::unique_ptr<A> up1(new A);
	// 这样的拷贝行为违反了 unique_ptr 的意图,unique_ptr 应该是独占资源的智能指针
	// aiyimu::unique_ptr<A> up2(up1);
	up1->_a1++;
	up1->_a2++;
	
	cout << "up1->_a1: " << up1->_a1 << endl;
	cout << "up1->_a2: " << up1->_a2 << endl;
	// 输出结果: 1 1
}

总结:unique_ptr 是一种独占所有权的智能指针,不允许直接进行拷贝行为。如果需要共享资源,可以使用 shared_ptr 来实现。


shared_ptr

性质

  1. 共享性:shared_ptr 可以与其他 shared_ptr 共享所管理的资源。通过内部的引用计数机制,shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
  2. 拷贝语义:shared_ptr 支持拷贝构造函数和拷贝赋值运算符。当一个 shared_ptr 被拷贝给另一个 shared_ptr 时,它们将共享同一个资源,引用计数会增加
  3. 自动释放:当最后一个引用计数为零时,即没有任何 shared_ptr 实例指向某个资源时,shared_ptr 自动释放资源。这可以避免了资源泄漏。
  4. 循环引用处理:shared_ptr 使用弱引用计数(weak_ptr)来解决循环引用问题。循环引用是指两个或多个对象相互持有对方的 shared_ptr 实例,导致引用计数无法归零。通过将其中一个 shared_ptr 转换为 weak_ptr,可以打破循环引用,使资源正确释放。
  5. 定制删除器:与 unique_ptr 类似,shared_ptr 也 支持定制的删除器 ,以实现对不同类型资源的特殊释放操作。

其中的重点在于引用计数:

  • 对象被销毁时(析构函数调用),资源不再使用,对象的引用计数减一
  • 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源
  • 如果引用计数不是0,就说明仍有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了

代码分析

namespace aiyimu
{
	// shared_ptr 是 C++ 中一种共享所有权的智能指针。
	// 与 unique_ptr 只能由一个对象拥有所有权不同,shared_ptr 允许多个 shared_ptr 对象同时管理同一个对象
	template<class T>
	class shared_ptr
	{
	public:
		// 构造
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			,_pCount(new int(1))
		{}

		// 析构
		~shared_ptr()
		{
			Release();
		}

		// 拷贝构造
		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			,_pCount(sp._pCount)
		{
			++(*_pCount); // 计数++
		}

		// 赋值重载
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			// 自己赋值自己,不做操作
			if (_ptr == sp._ptr)
			{
				return *this;
			}

			Release();

			// 共享新资源,计数++
			_ptr = sp._ptr;
			_pCount = sp._pCount;

			(*_pCount)++;

			//返回*this
			return *this;
		}

		T& operator*()
		{
			assert(_ptr != nullptr); // 断言指针非空
			return *_ptr;
		}

		T* operator->()
		{
			assert(_ptr != nullptr); // 断言指针非空
			return _ptr;
		}

		// 返回计数个数
		int use_count()
		{
			return *_pCount;
		}

		// 获取指针
		T* get() const
		{
			return _ptr;
		}

		// Release 函数,用于释放资源并销毁 shared_ptr 对象
		void Release() {
			if (--(*_pCount) == 0) 
			{
				cout << "Delete:" << _ptr << endl;
				delete _ptr;
				delete _pCount;
			}
		}

	private:
		T* _ptr;
		int* _pCount; // 引用计数
	};
};

下面的代码将展示 shared_ptr 的使用 和 性质验证:

void test_shared_ptr1()
{
	aiyimu::shared_ptr<A> sp1(new A);
	aiyimu::shared_ptr<A> sp2(sp1);
	aiyimu::shared_ptr<A> sp3(sp1);

	sp1->_a1++;
	sp1->_a2++;
	cout << "sp2->_a1 : _a2 ->" << sp2->_a1 << ":" << sp2->_a2 << endl; // 1 1
	// shared_ptr 使sp1 sp2 共享一块内存,即两者的_a1,_a2的值是同步的
	sp2->_a1++;
	sp2->_a2++;
	cout << "sp1->_a1 : _a2 ->" << sp1->_a1 << ":" << sp1->_a2 << endl; // 2 2
}

下面将介绍 与 shared_ptr 配合使用的 weak_ptr


weak_ptr

weak_ptr 是 C++ 标准库(C++11 及以后版本)中与 shared_ptr 配合使用的智能指针类, 用于解决 shared_ptr 的循环引用问题

特性

  • 弱引用weak_ptr 是一种弱引用,它可以观测(但不拥有)一个由 shared_ptr 管理的对象。通过 shared_ptr 创建 weak_ptr,可以同时存在多个 weak_ptr 实例观测同一个资源。
  • 不会增加引用计数weak_ptr 不会增加所管理资源的引用计数。即使存在 weak_ptr 对象观测某个资源,资源的引用计数也不会增加,因此不会影响资源的生命周期。
  • 检查资源是否有效:可以使用 expired() 函数检查 weak_ptr 所观测的资源是否还存在。如果资源已经被释放(即引用计数为零),expired() 返回 true,否则返回 false。
  • 获取共享指针:可以使用 lock() 函数将 weak_ptr 转换为 shared_ptr,得到与之关联的共享指针。如果资源仍然存在,则返回一个有效的 shared_ptr;如果资源已被释放,则返回一个空的 shared_ptr。

代码分析

下面是一个简化版本的 weak_ptr 的模拟实现:

// weak_ptr 是 C++ 中一种弱引用智能指针,用于解决 shared_ptr 的循环引用问题。
	template<class T>
	class weak_ptr
	{
	public:
		weak_ptr()
			:_ptr(nullptr)
		{}

		// 该构造函数接受一个shared_ptr<T>类型的参数sp,将其内部指针通过get()函数获取后赋值给_ptr。
		// 可以创建一个weak_ptr对象来观测所传入的shared_ptr所管理的资源。
		weak_ptr(const shared_ptr<T>& sp)
			:_ptr(sp.get())
		{}

		// 该构造函数接受一个weak_ptr<T>类型的参数wp,将其内部指针直接赋值给_ptr。
		// 可以创建一个新的weak_ptr对象,其观测的资源与原wp对象相同。
		weak_ptr(const weak_ptr<T>& wp)
			:_ptr(wp._ptr)
		{}

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

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

		// 赋值
		weak_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			_ptr = sp.get();
			return *this;
		}

	private:
		T* _ptr;
	};

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

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

相关文章

MySQL主从复制原理及配置

目录 一、MySQL主从复制原理 1、什么是主从复制 2、主从复制原理 二、主从复制配置 1、主服务器数据库配置 &#xff08;1&#xff09;设置server-id值并开启binlog参数&#xff0c;启用二进制日志功能后&#xff0c;重启数据库。 &#xff08;2&#xff09;建立同步账号&a…

LabVIEW基础-lvlib库

文章目录 lvlib库llb库lvlib与llb的区别lvlib常见错误断开vi与库之间的连接 lvlib库 文件-新建-库&#xff0c;创建一个项目库文件。能在项目中创建的文件类型&#xff0c;都可以在库中创建。 在lvlib上右键-添加-文件&#xff0c;将被选中的文件放到lvlib中。被添加进lvlib的…

【PostgreSQL内核学习(八)—— 查询执行(查询执行策略)】

查询执行 查询执行概述查询执行策略可优化语句和数据定义语句四种执行策略策略选择实现Portal执行的过程 声明&#xff1a;本文的部分内容参考了他人的文章。在编写过程中&#xff0c;我们尊重他人的知识产权和学术成果&#xff0c;力求遵循合理使用原则&#xff0c;并在适用的…

大数据分析案例-基于LightGBM算法构建乳腺癌分类预测模型

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

即时通信的方法和webSocket的具体使用

前言 之前遇到过需要即时通讯的场景&#xff0c;刚开始使用的是通过轮询的方式&#xff0c;定时器3秒向服务器请求一次数据&#xff0c;后面发现如果在手机端长时间打开使用此功能的页面&#xff0c;可能会发生手机发热&#xff0c;甚至卡顿的现象。最后改用webSocket&#xf…

js 过滤两个数组中的相同元素

1. filter 和 find 、some ,includes结合使用 let arr [1, 2, 3, 4, 5]; let arr2 [3, 4, 5, 6, 7];const arr3 arr.filter((item) > arr2.includes(item)); const arr4 arr.filter((item) > arr2.find((item2) > item2 item)); const arr5 arr.filter((item)…

Vue3+ElementPlus+TS实现右上角消息数量实时更新

Vue3ElementPlusTS实现右上角消息数量实时更新 背景 项目需求&#xff0c;前端右上角铃铛图标 显示接收到的消息通知&#xff0c;并且显示消息数量以及实时更新。&#xff08;一般是点击操作按钮后增加一条消息通知&#xff0c;图标上的数字也随之更新&#xff09; 【原来的想…

uniapp 微信小程序 姓名脱敏 substring报错问题:Cannot read property ‘substring‘ of undefined

效果图&#xff1a; 刘德华----------刘* 加v-if判断是因为如果是后台数据返回的字段&#xff0c;如果不加判断&#xff0c;substring的时候有可能数据还没渲染完&#xff0c;会报错 <text v-if"userName">{{userName.substring(0, 1) *}}</text>

【Duilib】错误:红色波浪线,无法打开源文件“stdafx.h”

问题 Duilib工程可以编译&#xff0c;但是智能提示&#xff1a;错误&#xff1a;红色波浪线&#xff0c;无法打开源文件“stdafx.h”。 解决方法 1、确认stdafx.h文件位置在vcxproj文件同一目录&#xff0c;并且stdafx.h已添加至DUILIB工程。 2、DUILIB项目属性\C/C\常规 页面…

(css)原生html实现遮罩层弹窗

(css)原生html实现遮罩层弹窗 效果&#xff1a; html <div class"overlay"><div class"content"><!-- 需要遮罩的内容 --> <el-table :data"tableData" size"mini" class"table-class" border stripe…

AutoSAR系列讲解(实践篇)7.4-实验:配置SWCRTE

注意: 实验篇是重点,有条件的同学最好跟着做一遍,然后回头对照着7.1-7.3理解其配置的目的和意义。实验下篇将在7.7节中继续做 一、实验概览 1、实验目的 通过本次实验,主要是让大家对Dev的配置有一个全流程的学习。这里会用到前两节的内容,将其串联起来,让大家能完整的…

【git基本使用】

初识git 一、git安装 1.1 Linux-centos 如果你的的平台是centos&#xff0c;安装git相当简单&#xff0c;以我的centos7.6为例&#xff1a; ⾸先&#xff0c;你可以试着输⼊Git&#xff0c;看看系统有没有安装Git&#xff1a; git-bash: git: command not found 出现像上⾯…

linux同时安装JDK8和JDK11并指定默认版本

1、安装OpenJDK11 yum install java-11-openjdk2、安装OpenJDK 8 yum install java-8-openjdk3.查看Java版本列表 alternatives --list4、查看当前Java版本 java -version5、更改Java版本 alternatives --config java6、设置JAVA_HOME环境变量 vi /etc/profile.d/java.sh…

多线程(JavaEE初阶系列3)

目录 前言&#xff1a; 1.中断一个线程 2.等待一个线程-join() 2.1join()无参调用的使用 2.2join()有参调用的使用 3.线程的状态 3.1观察线程的所有状态 4.多线程带来的风险—线程安全 4.1观察线程不安全 4.2该问题出现的原因 4.3线程不安全问题的解决 4.3.1synchro…

【PostgreSQL内核学习(九)—— 查询执行(数据定义语句执行)】

数据定义语句执行 概述数据定义语句执行流程执行示例 声明&#xff1a;本文的部分内容参考了他人的文章。在编写过程中&#xff0c;我们尊重他人的知识产权和学术成果&#xff0c;力求遵循合理使用原则&#xff0c;并在适用的情况下注明引用来源。 本文主要参考了《PostgresSQL…

【Python数据分析】Python基础知识篇

&#x1f389;欢迎来到Python专栏~Python基础知识篇 ☆* o(≧▽≦)o *☆嗨~我是小夏与酒&#x1f379; ✨博客主页&#xff1a;小夏与酒的博客 &#x1f388;该系列文章专栏&#xff1a;Python学习专栏 文章作者技术和水平有限&#xff0c;如果文中出现错误&#xff0c;希望大…

vue中使用原生的table合并行

完整的代码&#xff1a; <template><table border"1"><thead><tr><th>Name</th><th>Value</th></tr></thead><tbody><template v-for"(item, index) in tableData"><templat…

Hbase基本原理剖析

一、基本原理 数据存储使用HBase来承接&#xff0c;HBase是一个开源的、面向列&#xff08;Column-Oriented&#xff09;、适合存储海量非结构化数据或半结构化数据的、具备高可靠性、高性能、可灵活扩展伸缩的、支持实时数据读写的分布式存储系统。更多关于HBase的信息&#…

fpga4fun—发光二极管

发光二极管电子基础知识 LED&#xff08;发光二极管&#xff09;是一种半导体器件&#xff0c;当电流通过它时会产生光。 LED 符号看起来像一个二极管&#xff0c;带有阳极 &#xff08;&#xff09; 和阴极 &#xff08;-&#xff09;。 LED 的作用类似于二极管 - 单向导电&…

电脑新装系统优化,win10优化,win10美化

公司发了新的笔记本&#xff0c;分为几步做 1.系统优化,碍眼的关掉。防火墙关掉、页面美化 2.安装必备软件及驱动 3.数据迁移 4.开发环境配置 目录 目录复制 这里写目录标题 目录1.系统优化关掉底部菜单栏花里胡哨 2.安装必备软件及驱动新电脑安装360 1.系统优化 关掉底部菜单…