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

c++:智能指针

时间:2024-11-05 20:44:10浏览次数:3  
标签:std 引用 c++ 智能 内存 shared ptr 指针

文章目录


前言

智能指针(smart pointer)是一种用来防止内存泄漏的编程技术,它利用对象管理资源的方式(又名RAII——Resource Acquisition Is Initialization),即利用对象析构函数在生命周期结束时自行调用的特性,让编译器自行释放动态开辟的空间。


一、内存泄漏

1.1 内存泄漏的定义

内存泄漏通常发生在使用了new或malloc等函数动态分配内存后,忘记了对应的delete或free调用,或者是因为逻辑错误导致这些调用未能执行。这种情况下,程序占用的内存会逐渐增加,最终可能导致系统资源耗尽,程序崩溃或运行缓慢。

1.2 内存泄漏的常见原因

1.忘记释放内存:程序员在堆上动态分配内存后,忘记或未能正确地释放它。
2.异常处理不当:当程序发生异常时,执行流程可能会发生改变,导致某些正常流程中的内存释放操作无法执行


void MemoryLeaks()
{
	// 1.内存申请了忘记释放
	int* p1 = (int*)malloc(sizeof(int));
	int* p2 = new int;
 
	// 2.异常安全问题
	int* p3 = new int[10];
	Func(); // 这里Func函数如果抛异常就会导致 delete[] p3未执行,p3没被释放.
	delete[]p3;
}

3.拷贝构造函数和赋值运算符未正确处理动态内存:当类对象包含动态分配的内存时,拷贝构造函数和赋值运算符需要特别处理,以确保在对象复制或赋值时不会导致内存泄漏。
4.容器使用不当:C++标准库提供了许多容器类(如std::vector、std::string等),它们内部自动管理内存。然而,如果在使用这些容器时不当(如将一个容器作为另一个容器的元素,并在某个时刻改变了容器的容量),也可能导致内存泄漏。

5.循环引用:两个或多个对象相互引用,形成一个循环,导致内存无法被释放。(往下会介绍此种泄露的原因和解决办法)

1.3内存泄漏的危害

内存泄漏会导致程序占用的内存逐渐增加,最终可能导致系统资源耗尽,程序崩溃或运行缓慢。此外,内存泄漏还可能引发其他严重问题,如内存碎片、系统不稳定等
智能指针的出现就是解决这种危害的一种方法。

二、智能指针的用法和模拟实现

2.1 RAII

实际上RAII就是利用一个类来进行动态开辟的资源的管理,这个类是一个模板类,我们只需要在实例化这个类的时候传需要被管理的资源的类型,如此就可以很好的解决内存泄漏问题
RAII(Resource Acquisition Is Initialization)是一种管理资源的技术,它利用C++对象的生命周期来自动管理资源。RAII的核心思想是,将资源的获取(如动态内存分配、文件打开、互斥锁锁定等)与对象的构造绑定在一起,而将资源的释放(如内存释放、文件关闭、互斥锁解锁等)与对象的析构绑定在一起。这样,当对象超出其作用域或被显式销毁时,资源会自动被释放

2.1.1 RAII的工作原理

1.资源获取与对象构造:
当一个对象被构造时,它会自动获取所需的资源。这通常是通过调用构造函数中的代码来实现的。
构造函数负责确保资源被正确分配和初始化。

2.资源使用与对象生存:
在对象的生命周期内,资源可以被安全地使用。
对象的成员函数可以访问和操作这些资源。

3.资源释放与对象析构:
当对象被销毁时(例如,超出其作用域、被显式删除或作为另一个对象的成员而被销毁),它的析构函数会被自动调用。
析构函数负责释放资源,确保不会发生内存泄漏或其他资源泄漏。

2.1.2 RAII的优点

1.自动管理资源:RAII通过对象的生命周期来自动管理资源,减少了手动管理资源的复杂性和出错的可能性。
2.异常安全性:由于资源的释放是在析构函数中进行的,而析构函数在异常传播过程中总是会被调用,因此RAII提供了异常安全性。即使程序在资源使用期间抛出异常,资源也会被正确释放。

3.简化代码:使用RAII可以简化资源管理代码,使代码更加清晰和易于维护

2.2 智能指针的原理和设计思路

智能指针是一种能够自动管理指针指向内存的类模板。内部使用(RAII的原理)来自动释放内存。

智能指针中会重载operator*和opertaor->,具有像指针一样的行为

2.3 智能指针的种类和特点

智能指针主要分为两类:不带引用计数的智能指针和带引用计数的智能指针。d都包含在c++的标准库中的 memory 下面。

不带引用计数的智能指针:
auto_ptr :C++98标准库提供的智能指针,但由于其管理权会转移的特性,容易导致程序崩溃,因此不推荐使用。
scoped_ptr :C++11标准库提供的智能指针,防止拷贝,确保资源在同一作用域内被管理,离开作用域时自动释放资源。但C++17中已被弃用。
unique_ptr:C++11标准库提供的智能指针,不允许拷贝,但允许移动,确保资源的唯一所有权,离开作用域时自动释放资源。

带引用计数的智能指针:
shared_ptr :多个智能指针可以管理同一个资源,每个资源匹配一个引用计数。当智能指针指向该资源时,引用计数加1;当该指针不再使用该资源时,引用计数减1。当引用计数为0时,释放资源。shared_ptr是线程安全的,可直接用于多线程环境。但需要注意循环引用的问题。
weak_ptr :为了解决shared_ptr的循环引用问题而设计的。weak_ptr指向已被shared_ptr管理的资源时,不会增加其引用计数。在weak_ptr解除指向资源时,也不会减少引用计数。因此,weak_ptr不能单独管理资源,必须与shared_ptr配合使用。

2.3.1 std::auto_ptr

auto_ptr是在C++98版本中就给出的,它的实现原理是:管理权转移,只有一个对象能够管理资源(意味着转移后的那个指针会被悬空)
我们先来看看其模拟代码实现以及使用

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)
	{
		// 检测是否为自己给自己赋值(这一步很重要,不然容易发生双重释放)
		//一旦使用delete或delete[]释放了内存,就不应该再次尝试释放同一块内存。
		//这会导致未定义行为,可能包括程序崩溃。
		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()
{
  auto_ptr<int> s1(new int);
  auto_ptr<int> s2=s1;//此时s1为空指针,s2获取到了s1申请的资源
  //这份资源会随着程序结束时随着对象的生命周期结束而调用析构函数自动的析构
}

在这里插入图片描述

2.3.2 std::unique_ptr

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


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;
	}
	//将它的赋值和拷贝都禁止,就可以保证管理权无法转移
    //delete关键字:声明函数时用到这个可以使这个函数无意义
    //意味着禁止生成编译器本来会默认生成的函数
	unique_ptr(const unique_ptr<T>& sp) = delete;
	unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;
private:
	T* _ptr;
};

2.3.3 std::shared_ptr

std::shared_ptr是共享智能指针,它允许多个智能指针共享同一块内存。std::shared_ptr内部使用引用计数来管理内存,当引用计数为0时,内存会被自动释放。

	//引用计数
	template<class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr=nullptr)
			:_ptr(ptr)
			,_pcount(new int(1))
		{}
		~shared_ptr()
		{
			if(--(*_pcount)==0)
			{
				cout << "~shared_ptr()" << endl;
				delete _ptr;
				delete _pcount;
			}
		}
		int use_count()
		{
			return *_pcount;
		}
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
 
		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)
			{
				if (--(*_pcount) == 0)
				{
					delete _ptr;
					delete _pcount;
				}
				_ptr = sp._ptr;
				(*sp._pcount)++;
				_pcount = sp._pcount;
				return *this;
			}
		}
	private:
		T* _ptr;
		int* _pcount;
	};

声明方式

#include <memory>
 
int main() {
   shared_ptr<int> ptr1(new int(10));
    shared_ptr<int> ptr2 = ptr1;
    // 使用ptr1和ptr2
    // ...
    return 0;
}

2.3.4 std::weak_ptr

std::weak_ptr是弱指针,它用于解决共享指针可能导致的循环引用问题。弱指针不会增加引用计数,因此不会阻止内存的释放。

循环引用发生的场景如下:

在这里插入图片描述

结合share_ptr的引用计数我们可以总结出如下规律:循环引用会导致内存泄漏,因为相互引用的对象无法被正确地销毁。即使程序不再需要这些对象,由于它们的引用计数始终大于零

std::weak_ptr是一种不控制所指向对象生命周期的智能指针,它指向一个由std::shared_ptr管理的对象,但不会增加对象的引用计数。因此,将循环引用中的一个std::shared_ptr替换为std::weak_ptr可以打破循环引用,从而避免内存泄漏。

	//weak_ptr
	//不支持RAII
	template<class T>
	class weak_ptr
	{
	public:
		weak_ptr()
			:_ptr(nullptr)
		{}
		weak_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
		{}
		//相比于share_ptr没有增删引用计数的数目
		weak_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			_ptr = sp.ptr;
			return *this;
		}
		//像指针一样
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _ptr;
	};

三. 智能指针使用时注意事项

  1. 初始化:智能指针必须通过new操作符或构造函数进行初始化。并且不能以=来进行赋值,因为编译器会进行隐式类型转换。
    赋值:智能指针之间可以相互赋值,但std::unique_ptr不能赋值给std::shared_ptr。
  2. 解引用:使用解引用运算符(*)和箭头运算符(->)来访问智能指针指向的内存。
  3. 重置:使用reset方法来重置智能指针,释放当前指向的内存,并可以重新指向新的内存。

总结

以上知识点具体的使用还需结合实际情况,仍是道阻且长。

标签:std,引用,c++,智能,内存,shared,ptr,指针
From: https://blog.csdn.net/make_day_day_up/article/details/143512269

相关文章

  • 什么是C++模板,有哪些类型的模板?
    模板C++模板是一种强大的语言特性,允许开发者编写与类型无关的代码,从而实现代码的复用和灵活性。通过模板,可以定义函数和类,具体实现将由具体的类型实例化决定。函数模板函数模板(FunctionTemplates):函数模板用于定义一个通用的函数,该函数可以接受任意类型的参数。通过使用模......
  • c++知识及编译调试
    文章目录c++知识指针查找内存模型引用函数编译调试1.编译选项2.静态库和动态库3.gdb调试代码1.通讯录c++知识指针常量(的)指针constint*p=&a;指针指向可改,指向的值不可改。指针常量int*constp=&a;指针不能改,指向的值可改。查找unordered_setit!=uset.e......
  • C++20 STL CookBook 4:使用range在容器中创建view
    目录rangeviewrange_adaptor的三个概念以std::string和std::string_view为例子初次入手补充ranges的一些操作rangeviewrange_adaptor的三个概念新的范围库是C++20中更重要的新增功能之一。它为过滤和处理容器提供了新的范例。范围为更有效和可读的代码提供了简......
  • C语言第11节:指针(1)
    1.内存和地址1.1内存内存是计算机系统中用于存储数据和指令的硬件设备。它可以被视为一个巨大的、有序的字节数组。基本单位:内存的基本单位是字节(byte)。每个字节由8个位(bit)组成,可以存储0到255之间的一个数值。内存模型:从程序员的角度来看,内存可以被想象成一个巨大的一......
  • Rockchip SoC 赋能 AI 与视觉创新:推动智能设备的未来发展
    随着人工智能(AI)和计算机视觉技术不断推动各行各业的创新,Rockchip已成为提供强大系统级芯片(SoC)解决方案的领先厂商。该公司已开发出多款集成AI功能并支持先进多媒体与视觉技术的SoC,非常适合用于AI驱动的边缘计算、安全监控、机器人技术等应用领域。armsomboard本文将探讨......
  • 智能 AI 视觉识别系统打造高效流量统计方案
    智能AI视觉算法解决方案,涵盖客流人数统计、车流量统计、牲畜养殖场计数、物品点包计数、超员报警、火焰识别报警及驾驶行为报警等功能。可精准统计商场、车站等地客流,区分车型统计车流量并预警拥堵,准确计数牲畜及物品,检测工厂超员并辅助管理,快速响应火焰降低损失,判断行为规范保......
  • 服务区智慧公厕厂家引领公共卫生间智能化新时代
    随着科技的飞速发展,智能化已经成为公共设施发展的新趋势。在这一背景下,服务区智慧公厕厂家凭借先进的技术和创新的产品,引领公共卫生间进入智能化新时代。本文将从四个主要方面介绍服务区智慧公厕厂家如何推动公共卫生间智能化的发展。一.厕位检测与用户指引1.传感器技......
  • RFID实现机场智能分拣
    一、RFID技术在机场分拣中的应用概述1.1RFID技术基本原理RFID(RadioFrequencyIdentification)技术,即无线射频识别技术,是一种非接触式的自动识别技术,通过无线电波实现对标签的识别和数据的读写,无需直接接触或视线可见。RFID系统主要由电子标签(Tag)、读写器(Reader)和天线(Antenna......
  • 合规监管新趋势!智能合同审查免费体验,提升法律行业合同管理效率
    思通数科思通数据思通数科推出的智能合同审查系统为这些痛点提供了解决方案。借助大模型、OCR和信息抽取等先进技术,系统具备高效要素抽取、自动分类、合同比对和合同审查等功能。以下是该系统的主要功能及用户体验案例,展示了在法律行业中的实际应用效果。应用案例:法律行业智能......
  • 如何有效将人工智能技术引入小学
    Q:使用AI后教师如何维持对课堂的掌握和影响力?(一)教学内容和目标方面1明确教学目标教师在使用AI辅助教学后,依然要明确每节课的教学目标。AI可以提供丰富的教学资源和学习路径建议,但教师要根据课程标准和学生的实际情况,确定具体的知识、技能和情感目标。例如,在一节英语阅读课上,......