c++进阶之----智能指针

news2025/4/15 9:07:50

1.概念

在 C++ 中,智能指针是一种特殊的指针类型,它封装了裸指针(raw pointer)的行为,并通过 RAII(Resource Acquisition Is Initialization,资源获取即初始化)机制自动管理动态分配的内存。智能指针的主要目的是简化内存管理,避免内存泄漏、悬空指针等问题

2.执行机制

RAII在获取资源时把资源委托给一个对象,接着控制对资源的访问,资源在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源,这样保障了资源的正常释放,避免资源泄漏问题。
智能指针类除了满足RAII的设计思路,还要方便资源的访问,所以智能指针类还会想迭代器类一
样,重载 operator*/operator->/operator[] 等运算符,方便访问资源。

3.实现代码

实现过程就是前面类和对象的内容,只不过将其与异常处理杂糅起来了,具体细节见代码注释!

#include <iostream>
#include<functional>
using namespace std;
template <class T>
class SmartPtr
{
public:
	//RAII
	SmartPtr(T* ptr)
		:_ptr(ptr)
	{}

	~SmartPtr()
	{
		cout << "delete[] " << _ptr << endl;
		delete[] _ptr;
	}

	// 重载运算符,模拟指针的行为,方便访问资源
	T& operator*()
	{
		return  *_ptr;
	}

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

	T& operator[](size_t i)
	{
		return _ptr[i];
	}
private:
	T* _ptr;
};

double Divide(int a, int b)
{
	// 当b == 0时抛出异常
	if (b == 0)
	{
		throw "Divide by zero condition!";
	}
	else
	{
		return (double)a / (double)b;
	}
}

void func()
{
	// 这里使用RAII的智能指针类管理new出来的数组以后,程序简单多了
	SmartPtr<int> sp1 = new int[10];
	SmartPtr<int> sp2 = new int[10];

	//SmartPtr<int> sp3(sp1);     //通过sp1构造sp3      
	// 默认的拷贝构造函数和赋值运算符会进行浅拷贝,这会导致多个 SmartPtr 对象指向同一个资源。
	// 当这些对象被销毁时,多个析构函数会尝试释放同一个指针,导致未定义行为(通常是程序崩溃)。
	//所以记得把他注释掉,只是说可能怎么写,但是超级不建议

	for (size_t i = 0; i < 10; i++)
	{
		sp1[i] = sp2[i] = i;
	}

	int len, time;
	cin >> len >> time;
	cout << Divide(len, time) << endl;
}

int main()
{
	try
	{
		func();
	}
	catch (const char* errmsg)
	{
		cout << errmsg << endl;
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
	catch (...)
	{
		cout << "未知异常" << endl;
	}
	return 0;
}

对比一下运行结果:

 

这是调试时的界面,抛出的异常会被这个catch捕捉 

4.智能指针的种类

C++ 标准库提供了几种常见的智能指针类型:

4.0 std::auto_ptr

auto_ptr 是C++98时设计出来的智能指针,他的特点是拷贝时把被拷贝对象的资源的管理权转移给
拷贝对象,这是一个非常糟糕的设计,因为他会到被拷贝对象悬空,访问报错的问题,C++11设计
出新的智能指针后,强烈建议不要使⽤auto_ptr。其他C++11出来之前很多公司也是明令禁止使用
这个智能指针的。

4.1 std::unique_ptr

std::unique_ptr 是一种独占所有权的智能指针,表示它所指向的资源只能被一个 unique_ptr 对象拥有。当 unique_ptr 被销毁时,它会自动释放所管理的资源。

  • 特点

    • 不可复制(copy),但可以移动(move)。

    • 适合单所有权场景。

    • 性能开销极小,几乎和裸指针相当。

4.2 std::shared_ptr

std::shared_ptr 是一种引用计数的智能指针,允许多个 shared_ptr 对象共享对同一资源的所有权。当最后一个 shared_ptr 被销毁时,资源会被自动释放。

  • 特点

    • 支持复制(copy)。

    • 支持移动(move)

    • 适合共享所有权场景。

    • 性能开销稍高,因为需要维护引用计数(底层实现)。

4.3 std::weak_ptr

std::weak_ptr 是一种弱引用指针,通常与 std::shared_ptr 一起使用。它观察一个资源,但不增加引用计数。weak_ptr 主要用于打破 shared_ptr 的循环引用问题。

  • 特点

    • 不增加引用计数。

    • 适合观察资源,但不希望影响资源的生命周期。

4.4 代码示例

我们先实现一个日期类

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

	Date(int year = 1, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day)
	{}

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

 4.4.1 auto_ptr 

int main()
{
	//创建一个 auto_ptr<Date> 对象 ap1,它管理一个动态分配的 Date 对象
	auto_ptr<Date> ap1(new Date);
	// 拷贝时,管理权限转移,被拷贝对象ap1悬空
	//在 auto_ptr 的拷贝构造中,ap1 的所有权会被转移到 ap2,ap1 会变成一个悬空指针(即它不再管理任何资源)。
	auto_ptr<Date> ap2(ap1);
	//尝试通过 ap1 访问资源,但此时 ap1 已经是一个悬空指针,导致未定义行为(通常是程序崩溃或异常)
	ap1->_year++;
	return 0;
}

运行上述代码,触发报错:

4.4.2 unique_ptr

int main()
{
	unique_ptr<Date> up1 (new Date);
	// 不支持拷贝
	//unique_ptr<Date> up2(up1);
	// 支持移动,但是移动后up1也悬空,所以使用移动要谨慎
	unique_ptr<Date> up3(move(up1));

	return 0;
}

运行代码:触发报错 

4.4.3 shared_ptr

int main()
{
	shared_ptr<Date> sp1(new Date);
	// 支持拷贝
	shared_ptr<Date> sp2(sp1);
	shared_ptr<Date> sp3(sp2);
	//use_count() 返回一个整数值,表示当前资源的引用次数。
	cout << sp1.use_count() << endl;

	sp1->_year++;

	cout << sp1->_year << endl;
	cout << sp2->_year << endl;
	cout << sp3->_year << endl;

	return 0;
}

运行一下:

说明:

shared_ptr 的引用计数机制中,析构函数的调用次数与引用计数无关,只与资源是否被释放有关。当引用计数变为0时,资源被释放,析构函数被调用一次。因此,即使引用计数为3,析构函数也只会调用一次。

4.4.4 weak_ptr

这里我们要了解三个函数:

  • expired():检查资源是否已过期。

  • use_count():返回共享该资源的 shared_ptr 对象的数量。

  • lock():获取一个 shared_ptr 对象,如果资源未过期。

int main()
{
	shared_ptr<string>  sp1(new string("11111111"));
	shared_ptr<string> sp2(sp1);// sp2与 sp1 共享同一个资源
	weak_ptr<string> wp = sp1;  //wp指向 sp1 的资源
	cout << wp.expired() << endl;    // 检查 wp 是否过期,输出 0(false),表示资源未过期
	cout << wp.use_count() << endl;  // 输出 2,表示 sp1 和 sp2 共享资源
	

	// sp1和sp2都指向了其他资源,则weak_ptr就过期了
	sp1 = make_shared<string>("222222");
	cout << wp.expired() << endl;// 输出 1(true),表示资源已过期
	cout << wp.use_count() << endl;// 输出 0,表示没有 shared_ptr 共享资源

	sp2 = make_shared<string>("333333");
	cout << wp.expired() << endl;    //同上
    cout << wp.use_count() << endl;

	wp = sp1;
	auto sp3 = wp.lock();  // 使用 wp.lock() 获取一个 shared_ptr 对象
	cout << wp.expired() << endl;   // 输出 0(false),表示资源未过期
	cout << wp.use_count() << endl;   // 输出 2,表示 sp1 和 sp3 共享资源

	*sp3 += "###";
	cout << *sp1 << endl;

	sp1 = sp2;
	return 0;
}

5. 手撕智能指针

由于我们要模拟引用计数,这里我们引入c++11的atomic,具体来说是atomic<int> ,是 C++11 中引入的一个模板类,用于支持原子操作。它确保对整数的操作是线程安全的,可以避免在多线程环境下出现数据竞争问题。 通常用于需要在多线程环境中进行原子操作的场景,比如计数器、标志位等。通过使用 atomic<int>,可以确保对变量的读取和写入操作是原子的,从而避免竞态条件

namespace rens
{
	template<class T>
	class my_shared_ptr
	{
	public:
		//在 C++ 中,explicit 是一个关键字,用于修饰构造函数,表示该构造函数只能通过直接初始化的方式调用,而不能通过隐式转换的方式调用。explicit 的主要目的是防止意外的隐式类型转换,从而提高代码的安全性和可读性。
		explicit my_shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			,_pcount(new std::atomic<int>(1))
		{}

		// 定制删除器,面试时手撕时,不要写删除器部分
		template<class D>
		my_shared_ptr(T* ptr, D del)
			: _ptr(ptr)
			, _pcount(new std::atomic<int>(1))
			, _del(del)
		{}

		//当一个 my_shared_ptr 对象被赋值为另一个对象时,先释放当前资源,
		// 然后复制新对象的资源,并增加引用计数。
		my_shared_ptr(const my_shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			, _pcount(sp._pcount)
		{
			++(*_pcount);
		}

		void release()
		{
			//如果引用计数为0,则释放资源。
			if (--(*_pcount) == 0)
			{
				//delete _ptr;
				_del(_ptr);// 定制删除器
				delete _pcount;
			}
		}

		// sp1 = sp3
		my_shared_ptr<T>& operator=(const my_shared_ptr<T>& sp)
		{
			if (_ptr != sp._ptr)
			{
				release();
				_ptr = sp._ptr;
				_pcount = sp._pcount;
				++(*_pcount);
			}
			return *this;
		}

		~my_shared_ptr()
		{
			release();
		}

		// 像指针一样使用
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
		int use_count() const
		{
			return *_pcount;
		}
		T* get() const
		{
			return _ptr;
		}
	private:
		T* _ptr;
		atomic<int>* _pcount;
		function<void(T*)> _del = [](T* ptr) {delete ptr; };
	};

	template<class T>
	class my_weak_ptr
	{
	public:
		my_weak_ptr()
		{}

		my_weak_ptr(const my_shared_ptr<T>& sp)
			:_ptr(sp.get)
		{}

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

	private:
		T* _ptr = nullptr;
	};
}

int main()
{
	rens::my_shared_ptr<Date> sp1(new Date);
	rens::my_shared_ptr<Date> sp2(sp1);

	rens::my_shared_ptr<Date> sp3(new Date);

	sp1 = sp1;
	sp1 = sp2;
	sp1 = sp3;
	sp2 = sp3;
	return 0;
}

补充:带删除器版本附加

template<class T>
void DeleteArrayFunc(T* ptr)
{
	delete[] ptr;
}
template<class T>
class DeleteArray
{
public:
	void operator()(T* ptr)
	{
		delete[] ptr;
	}
};

class Fclose
{
public:
	void operator()(FILE* ptr)
	{
		cout << "fclose:" << ptr << endl;
		fclose(ptr);
	}
};
int main()
{
	/*std::shared_ptr<Date[]> sp1(new Date[10]);
	std::unique_ptr<Date[]> up1(new Date[10]);*/

	// 定制删除器  函数指针/仿函数/lambda
	rens::my_shared_ptr<Date> sp2(new Date[5], DeleteArray<Date>());
	rens::my_shared_ptr<Date> sp3(new Date[5], DeleteArrayFunc<Date>);

	auto delArrOBJ = [](Date* ptr) {delete[] ptr; };
	rens::my_shared_ptr<Date> sp4(new Date[5], delArrOBJ);
	// 实现其他资源管理的删除器
	rens::my_shared_ptr<FILE> sp5(fopen("Test.cpp", "r"), Fclose());
	rens::my_shared_ptr<FILE> sp6(fopen("Test.cpp", "r"), [](FILE* ptr) {
		cout << "fclose:" << ptr << endl;
		fclose(ptr);
		});

	rens::my_shared_ptr<Date> sp7(new Date);

	//unique_ptr<Date, DeleteArray<Date>> up2(new Date[5]);
	//unique_ptr<Date, void(*)(Date*)> up3(new Date[5], DeleteArrayFunc<Date>);
	 decltype对象的类型
	//unique_ptr<Date, decltype(delArrOBJ)> up4(new Date[5], delArrOBJ);

	return 0;
}

 6.内存泄漏

6.1 定义

内存泄漏是指程序在申请内存后,无法释放已申请的内存,导致可使用的内存越来越少。随着时间的推移,内存泄漏会导致系统性能下降,甚至可能导致程序崩溃或系统不稳定。

6.2 内存泄漏的危害

6.2.1 系统性能下降

内存泄漏会导致可用内存逐渐减少,系统需要频繁地进行内存交换(swap),从而增加磁盘I/O操作,降低系统响应速度。随着时间的推移,系统可能会变得越来越慢,最终可能导致系统崩溃。

6.2.2 程序崩溃

当内存泄漏严重时,系统可能无法为其他程序或进程分配足够的内存,导致程序崩溃或无法启动。在多线程环境中,内存泄漏可能导致线程无法正常运行,从而影响整个程序的稳定性。

6.2.3 资源浪费

内存泄漏会导致系统资源的浪费,因为已分配的内存无法被其他程序或进程使用。长期的内存泄漏会导致系统资源的严重浪费,影响系统的整体性能。

6.2.4 安全性问题

在某些情况下,内存泄漏可能导致敏感数据(如密码、密钥等)驻留在内存中,增加数据泄露的风险。内存泄漏可能导致系统不稳定,从而影响系统的安全性。

6.3 内存泄漏的常见原因

  1. 未释放的动态内存:在 C 和 C++ 中,使用 mallocnew 分配的内存如果没有对应的 freedelete,就会导致内存泄漏。

  2. 循环引用:在使用引用计数的智能指针(如 std::shared_ptr)时,如果两个对象互相引用,可能会导致引用计数无法减到零,从而引发内存泄漏。

  3. 全局变量和静态变量:全局变量和静态变量在程序结束时才会被释放,如果程序运行时间较长,可能会导致内存泄漏。

  4. 异常处理不当:在异常处理中,如果未正确释放资源,可能会导致内存泄漏。

6.4 如何避免内存泄漏

  1. 使用智能指针:在 C++ 中,使用 std::unique_ptrstd::shared_ptr 等智能指针可以自动管理内存,避免手动释放内存。

  2. 代码审查:定期进行代码审查,检查是否有未释放的内存。

  3. 使用内存检测工具:使用内存检测工具(如 Valgrind、LeakSanitizer 等)可以帮助发现内存泄漏问题。

  4. 避免循环引用:在使用引用计数的智能指针时,避免循环引用,可以使用 std::weak_ptr 来打破循环引用。

  5. 良好的编程习惯

    • 确保每个 mallocnew 都有对应的 freedelete

    • 在异常处理中,确保资源被正确释放。

到此,我们c++主线课程内容结束!后续可能会更新c++ 副线内容,下一阶段将主要是linux以及算法课博客的更新!

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

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

相关文章

六、测试分类

设计测试用例 万能公式&#xff1a;功能测试性能测试界面测试兼容性测试安全性测试易用性测试 弱网测试&#xff1a;fiddler上行速率和下行速率 安装卸载测试 在工作中&#xff1a; 1.基于需求文档来设计测试用例&#xff08;粗粒度&#xff09; 输入字段长度为6~15位 功…

AI编程案例拆解|基于机器学习XX评分系统-前端篇

文章目录 1. 定价使用DeepSeek估价小红书调研 2. 确定工作事项利用DeepSeek生成具体工作事项 3. 和客户沟通约会议沟通确定内容样式 4. 前端部分设计使用DeepSeek生成UI设计在Cursor中生成并提问前置条件开始编程 关注不迷路&#xff0c;励志拆解100个AI编程、AI智能体的落地应…

java数组06:Arrays类

Arrays类 数组的工具类java.util. Arrays 由于数组对象本身并没有什么方法可以供我们调用,但API中是了一个工具类Arrays供我们使用,从而可以对数据对象进行一些基本的操作。 查看JDK帮助文档 Arrays类中的方法都是static修饰的静态方法,在使用的时候可以直接使用类名进行调用…

TQTT_KU5P开发板教程---实现流水灯

文档实现功能介绍 本文档是学习本开发板的基础&#xff0c;通过设置计数器使led0到led7依次闪烁&#xff0c;让用户初步认识vivado基本的开发流程以及熟悉项目的创建。本开发板的所有教程所使用的软件都是vivado2024.1版本的。可以根据网上的教程下载与安装。 硬件资源 此次教程…

Model Context Protocol(MCP)模型上下文协议

Model Context Protocol&#xff08;MCP&#xff09;模型上下文协议 前言一、什么是MCP二、MCP的作用三、MCP与Function call对比四、构建一个简单的MCP DEMO环境准备实现MCP Server运行 ServerMCP Client端配置验证 总结 前言 在Agent时代&#xff0c;将Agent确立为大模型未来…

第十二章:FreeRTOS多任务创建与删除

FreeRTOS多任务创建与删除教程 概述 本教程介绍FreeRTOS多任务的创建与删除方法&#xff0c;主要涉及两个核心函数&#xff1a; 任务创建&#xff1a;xTaskCreate()任务删除&#xff1a;vTaskDelete() 实践步骤 1. 准备工程文件 复制005工程并重命名为006 2. 创建多个任务…

Seed-Thinking-v1.5:字节豆包新推理模型发布,200B参数战胜Deepseek

摘要 本文引入了Seed-Thinking-v1.5&#xff0c;能够在响应之前通过思考进行推理&#xff0c;从而提高了各种基准测试的性能。Seed-Thinking-v1.5在AIME 2024上获得86.7分&#xff0c;在Codeforces上获得55.0分&#xff0c;在GPQA上获得77.3分&#xff0c;展示了优秀的STEM和编…

AIDD-人工智能药物设计-提升分子预测反事实解释可靠性

UQ 过滤:提升分子预测反事实解释可靠性 目录 I-INF 指标结合 F1 评分,为评估大分子复合物(包括 RNA-蛋白质)的界面相互作用网络提供了可靠且全面的新方法。通过使用生成的人工 CAR 序列微调蛋白质语言模型(PLM),显著提高了 CAR-T 细胞活性的预测准确性,有效克服了合成蛋…

【前端】webpack一本通

今日更新完毕&#xff0c;不定期补充&#xff0c;建议关注收藏点赞。 目录 简介使用webpack默认只能处理js文件 ->引入加载器对JS语法降级&#xff0c;兼容低版本语法合并文件再次打包进阶 工作原理html-webpack-plugin插件webpack开发服务器引入使用webpack-dev-server模块…

代码学习总结(一)

代码学习总结&#xff08;一&#xff09; 这个系列的博客是记录下自己学习代码的历程&#xff0c;有来自平台上的&#xff0c;有来自笔试题回忆的&#xff0c;主要基于 C 语言&#xff0c;包括题目内容&#xff0c;代码实现&#xff0c;思路&#xff0c;并会注明题目难度&…

第十五届蓝桥杯C/C++B组省赛真题讲解(分享去年比赛的一些真实感受)

试题A——握手问题 一、解题思路 直接用高中学的排列组合思路 二、代码示例 #include<bits/stdc.h> using namespace std; int fun(int n) {int sum0;for(int i0;i<n;i){for(int ji1;j<n;j)sum; } return sum; } int main() {cout<<fun(50)-fun(7); }三、…

【Qt】qDebug() << “中文测试“; 乱码问题

环境 Qt Creator版本&#xff1a;4.7.1 编译器&#xff1a;MSVC2015_32bit 解法一 在.pro文件中添加 msvc:QMAKE_CXXFLAGS -execution-charset:utf-8注意&#xff1a; 1、需要清理项目&#xff0c;并重新qmake&#xff0c;然后构建。 测试项目下载&#xff1a;https://do…

Vue接口平台学习六——接口列表及部分调试页面

一、实现效果图及界面布局简单梳理 整体布局分左右&#xff0c;左边调试&#xff0c;右边显示接口列表 左侧&#xff1a; 一个输入框按钮&#xff1b;下面展示信息&#xff0c;大部分使用代码编辑器就好了&#xff0c;除了请求体传文件类型需要额外处理。然后再下方显示响应信…

【C语言】预处理(下)(C语言完结篇)

一、#和## 1、#运算符 这里的#是一个运算符&#xff0c;整个运算符会将宏的参数转换为字符串字面量&#xff0c;它仅可以出现在带参数的宏的替换列表中&#xff0c;我们可以将其理解为字符串化。 我们先看下面的一段代码&#xff1a; 第二个printf中是由两个字符串组成的&am…

低频rfid手持机,助力动物耳标智能化管理

低频RFID手持机&#xff0c;助力动物耳标智能化管理&#xff0c;正逐步成为现代畜牧业不可或缺的工具。它不仅能够高效读取动物耳标中的信息&#xff0c;如唯一识别码、疫苗接种记录、健康状态等&#xff0c;还极大地提升了数据录入的准确性和时效性。 1.精准识别与追踪‌ 通过…

【从零开始学习JVM | 第三篇】虚拟机的垃圾回收学习(一)

堆空间的基本结构 Java 的自动内存管理主要是针对对象内存的回收和对象内存的分配。同时&#xff0c;Java 自动内存管理最核心的功能是 堆 内存中对象的分配与回收。 Java 堆是垃圾收集器管理的主要区域&#xff0c;因此也被称作 GC 堆&#xff08;Garbage Collected Heap&am…

Jieba分词的原理及应用(三)

前言 “结巴”中文分词&#xff1a;做最好的 Python 中文分词组件 上一篇文章讲了使用TF-IDF分类器范式进行企业级文本分类的案例。其中提到了中文场景不比英文场景&#xff0c;在喂给模型之前需要进行分词操作。 分词的手段有很多&#xff0c;其中最常用的手段还是Jieba库进行…

Openlayers:flat样式介绍

在前段时间我在使用WebGL矢量图层时接触到了flat样式&#xff0c;我对其十分的感兴趣&#xff0c;于是我花了几天的时间对其进行了了解&#xff0c;在这篇文章中我将简单的介绍一下flat样式的使用方式以及我对其的一些理解。 一、了解flat样式 1.什么是flat样式&#xff1f; …

149页研读——华为基于IPD全过程研发质量管理【附全文阅读】

本文介绍了IPD(集成产品开发)的全过程研发质量管理,强调了以客户需求为导向,通过跨部门协同、资源整合、快速响应等方式提高研发效率和成功率。文章详细阐述了IPD研发管理体系的精要,包括其核心思想、优势、框架以及核心理念。 其中,跨领域平台与技术研发、端到端流程与项…

Oracle 23ai Vector Search 系列之5 向量索引(Vector Indexes)

文章目录 Oracle 23ai Vector Search 系列之5 向量索引Oracle 23ai支持的向量索引类型内存中的邻居图向量索引 (In-Memory Neighbor Graph Vector Index)磁盘上的邻居分区矢量索引 (Neighbor Partition Vector Index) 创建向量索引HNSW索引IVF索引 向量索引示例参考 Windows 环…