[c++]智能指针(代码片段)

哦哦呵呵 哦哦呵呵     2022-12-29     579

关键词:

1. 为什么需要智能指针?

  我们在使用指针时一般都需要向内存申请一块内存空间进行使用,但是如果忘记对该块空间进行释放,就造成了内存泄漏
  并且如果在申请内存的使用时间,程序中有异常处理,并且抛出了异常,那么这个过程中没有进行空间的释放,同样造成了内存泄漏。

内存泄漏的危害

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

  所以为了避免上述情况的出现,引入了只能指针,使用智能指针进行资源的管理,就可以避免上述问题。

2. RAII (资源获取及初始化)

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

  巧妙利用编译器会自动调用构造函数以及析构函数的特性,来完成资源的自动释放。实际就是定义一个指针类,使用其构造函数与析构函数进行资源的管理。

构造函数: 在对象构造时获取资源,控制对资源的访问使之在对象的生命周期内始终保持有效。
析构函数: 释放资源

优点

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

2.1 RAII方式的原理

注意: 只能指针的类中,构造函数中不能申请空间,而是由用户在外部自己申请空间,传递到只能指针的类中去管理该资源。
  智能指针只是将资源管理起来,找一个合适的时机去释放。

// 使用RAII思想设计的SmartPtr类
template<class T>
class SmartPtr 
public:
	SmartPtr(T* ptr = nullptr)
		: _ptr(ptr)
	
	~SmartPtr()
		if(_ptr)
			delete _ptr;
	
	// 让该类对象具有指针类似的操作就可以了
	T& operator*()
		return *ptr;
	
	//  -> 只能指针指向对象或者结构体的这些场景中
	T* operator->()
		return ptr;
	
private:
	T* _ptr; // 采用类将指针管理起来
;
void MergeSort(int* a, int n)

	int* tmp = (int*)malloc(sizeof(int)*n);
	// 将tmp指针委托给了sp对象,给tmp指针找了一个可怕的女朋友!天天管着你,直到你go die^^
	SmartPtr<int> sp(tmp);
	// _MergeSort(a, 0, n - 1, tmp);
	
	// 这里假设处理了一些其他逻辑
	vector<int> v(1000000000, 10);
	// ...

  如上只是实现了资源的管理,但是该类不能使用指针独有的操作,所以需要重载指针的一些操作符。

2.2 重大问题

  上述代码中没有提供拷贝构造函数,那么如果进行拷贝时,会调用编译器提供的默认拷贝构造函数。智能指针不能申请资源,只能替用户管理资源,因此不能使用深拷贝来代替浅拷贝

如何解决: C++标准库中提供了,最优解,下面会介绍到。

2.3 智能指针原理

所以智能指针的原理: RAII + operator*() / operator->() + 解决浅拷贝问题

RAII: 能够保证资源可以被自动释放
operator()/operator->()*: 可以保证对象能够按照指针的方式来运行
解决浅拷贝的方式:可能保证资源不被释放多次而引起代码崩溃的问题

3. C++98 auto_ptr(不要用)

RAII + operator*() / operator->() + 解决浅拷贝问题

3.1 解决浅拷贝的方式一

auto_ptr: 解决浅拷贝使用的是资源转移

当发生拷贝或赋值时,将被拷贝对象中的资源转移给新对象,然后让被拷贝对象与资源断开联系

但是同时只能有一个指针被使用,另外一个指针就没有指向了

// 解决浅拷贝方式:资源转移
// auto_ptr<int>  ap2(ap1)
auto_ptr(auto_ptr<T>& ap)
	: _ptr(ap._ptr)

	ap._ptr = nullptr;


// ap1 = ap2;
auto_ptr<T>& operator=(auto_ptr<T>& ap)

	if (this != &ap)
	
		// 此处需要将ap中的资源转移给this
		// 但是不能直接转移,因为this可能已经管理资源了,否则就会造成资源泄漏
		if (_ptr)
		
			delete _ptr;
		

		// ap就可以将其资源转移给this
		_ptr = ap._ptr;
		ap._ptr = nullptr;   // 让ap与之前管理的资源断开联系,因为ap中的资源已经转移给this了
	

	return *this;

3.2 解决浅拷贝的方式二

资源管理权限转移

如果发生了拷贝或者赋值,不断开原来指针与资源块的联系,而是将资源的释放权限交给新指针,但是会造成野指针问题,因为新指针一旦析构,就会释放资源,而老指针还指向那块空间

4. C++11 unique_ptr

RAII + operator*() / operator->() + 解决浅拷贝问题

浅拷贝解决方式: 资源独占

禁止拷贝,一份资源只能让一个对象管理,对象间不能共享资源。

使用场景: 只能应用于资源被一个对象管理,并且资源不会共享的场景中。

如何实现
  让编译器不生成默认的拷贝构造以及赋值运算符的重载。

C++11中: delete关键字,可以用其修饰默认的成员函数,表明编译器不会生成修饰的成员函数。
C++98中: 将拷贝构造函数以及赋值运算符重载只声明不定义,并且把访问权限设置为私有。

只声明不定义,没有将权限设置为private,仍然可以在类外进行定义。

4.1 管理的资源多样如何释放

  管理的资源可能是从堆上申请的空间,也可能是文件指针等等

  定制删除器: 此处的释放方式不能写死,应该按照资源的不同类型对应不同的释放方式。

定义只能指针类时,模板参数加上资源释放的方式,用户自定义资源释放的类,自己指定释放资源的方法。在初始化智能指针时,就该把资源的释放方式确定下来,在初始化模板参数时就传入释放资源的类。

4.2 模拟实现

// 负责释放new资源
template<class T>
class DFDef

public:
	void operator()(T*& ptr)
	
		if (ptr)
		
			delete ptr;
			ptr = nullptr;
		
	
;

// 负责:malloc的资源的释放
template<class T>
class Free

public:
	void operator()(T*& ptr)
	
		if (ptr)
		
			free(ptr);
			ptr = nullptr;
		
	
;

// 关闭文件指针
class FClose

public:
	void operator()(FILE* & ptr)
	
		if (ptr)
		
			fclose(ptr);
			ptr = nullptr;
		
	
;


namespace test

	// T: 资源中所放数据的类型
	// DF: 资源的释放方式
	// 定制删除器
	template<class T, class DF = DFDef<T>>
	class unique_ptr
	
	public:
		/
		// RAII
		unique_ptr(T* ptr = nullptr)
			: _ptr(ptr)
		

		~unique_ptr()
		
			if (_ptr)
			
				// 问题:_ptr管理的资源:可能是从堆上申请的内存空间、文件指针、malloc空间...
				// delete _ptr; // 注意:此处的释放资源的方式不能写死了,应该按照资源类型不同找对应的方式释放
				// malloc--->free
				// new---->delete
				// fopen--->fclose关闭
				DF df;
				df(_ptr);
			
		
		// C++11: 可以让编译器不生成默认的拷贝构造以及赋值运算符重载---delete
		// 在C++11当中,delete关键字的功能扩展:释放new申请的空间  用其修饰默认成员函数,表明:编译器不会生成了
		 unique_ptr(const unique_ptr<T,DF>&) = delete;  // 表明:编译器不会生成默认的拷贝构造函数
		 unique_ptr<T,DF>& operator=(const unique_ptr<T,DF>&) = delete;// 表明:编译器不会生成默认的赋值运算符重载

	private:
		unique_ptr(const unique_ptr<T,DF>&);
		unique_ptr<T,DF>& operator=(const unique_ptr<T,DF>&);

	private:
		T* _ptr;
	;

	// 用户在外部可以对方法进行定义---在unique_ptr的类中如果将该权限设置为private的
	//template<class T>
	//unique_ptr<T>::unique_ptr(const unique_ptr<T>& up)
	//

5. C++11 shared_ptr

多个对象之间可以共享资源

5.1 如何解决浅拷贝

  采用引用计数的方式解决浅拷贝

引用计数: 实际是一个整型的空间,记录着资源的对象的个数。释放资源时,判断有没有其它对象在使用资源,没有的话就释放掉资源。

5.2 实现原理

拷贝时:

新对象要与之前的对象共享所有资源,并且计数器进行++

释放时:

1.先检测资源是否还存在
2.存在的话,先对计数器–,检测计数器当前是否为0

  • =0: 当前对象是最后使用该资源的对象,需要将资源以及计数器进行释放。
  • !=0: 还有其他对象在使用资源,当前对象不需要释放,如果释放了就会造成其它对象变成野指针。

5.3 多线程使用时的问题

  上述操作在单线程下不会出现问题,但是在多线程中有多个执行流。
  多个线程共享一份资源,多个线程在结束时需要将其管理的资源释放掉,多个线程可能会同时释放同一份空间,计数器的操作不是原子性的,所以多个线程判断当前资源都还有人在使用,导致资源没有释放,而引起内存泄漏。

   智能指针对象中引用计数是多个智能指针对象共享的,两个线程中智能指针的引用计数同时++或–,这个操作不是原子的,引用计数原来是1,++了两次,可能还是2.这样引用计数就错乱了。会导致资源未释放或者程序崩溃的问题。所以只能指针中引用计数++、–是需要加锁的,也就是说引用计数的操作是线程安全的

解决方案

1.加锁,保证计数器的操作是安全的
2.把计数器改为原子操作

C++11中的share_ptr,自身是线程安全的,标准库中是按照原子类型变量实现的 atomic_int

5.4 缺陷- 循环引用

struct ListNode

	shared_ptr<ListNode> next;
	shared_ptr<ListNode> prev;
	//weak_ptr<ListNode> next;
	//weak_ptr<ListNode> prev;
	int data;

	ListNode(int x)
		//: data(x)
		: next(nullptr)
		, prev(nullptr)
		, data(x)
	
		cout << "ListNode(int):" << this << endl;
	

	~ListNode()
	
		cout << "~ListNode():" << this << endl;
	
;

void TestLoopRef()

	shared_ptr<ListNode> sp1(new ListNode(10));
	shared_ptr<ListNode> sp2(new ListNode(20));

	cout << sp1.use_count() << endl;    // 1
	cout << sp2.use_count() << endl;    // 1

	sp1->next = sp2;
	sp2->prev = sp1;

	cout << sp1.use_count() << endl;    // 2
	cout << sp2.use_count() << endl;    // 2


上述代码导致的后果就是内存泄漏,释放资源时没有调用析构函数。

什么是循环引用

函数中sp1 ,sp2之间互相引用,两个资源的引用计数为2,当要跳出函数时,智能指针sp1 ,sp2析构时两个资源引用计数会减一,但是两者引用计数还是为1,导致跳出函数时资源没有被释放(析构函数没有被调用),如果把其中一个改为weak_ptr就可以了,我们把类种里面的shared_ptr 指针; 改为weak_ptr 指针; 运行结果如下,这样的话,资源B的引用开始就只有1,当pb析构时,B的计数变为0,B得到释放,B释放的同时也会使A的计数减一,同时pa析构时使A的计数减一,那么A的计数为0,A得到释放

5.5 weak_ptr

weak_ptr的作用,配合shared_ptr解决循环引用问题。weak_ptr不能独立管理资源
不控制对象生命周期,指向一个shared_ptr管理的对象,只是提供了对管理对象的一个访问手段。weak_ptr设计的目的为了配合shared_ptr解决循环引用,导致无法释放内存空间的问题。weak_ptr的构造和析构不会造成引用计数的增加或者减少

原理

实际上shared_ptr维护了两块计数器,一份用来记录shared_ptr的使用次数,一份用来记录weakptr的使用次数。weak_ptr指向资源,shared_ptr的use计数器不懂,weak计数器+1

销毁时

当一个资源被shared_ptr类型的对象共享时,给use+1
当一个资源被weak_ptr类型的对象时,给weak+1
销毁时: 先给use-1,确认资源能够释放,再给weak-1,确认资源能释放

5.5 代码实现

#include <mutex>

namespace test

	// shared_ptr: 自身才是安全的---加锁:为了保证shared_ptr自身的安全性
	template<class T, class DF = DFDef<T>>
	class shared_ptr
	
	public:
		//
		// RAII
		shared_ptr(T* ptr = nullptr)
			: _ptr(ptr)
			, _pCount(nullptr)
			, _pMutex(new mutex)
		
			if (_ptr)
			
				// 此时只有当前刚刚创建好的1个对象在使用该份资源
				_pCount = new int(1);
			
		

		~shared_ptr()
		
			Release();
		

		/
		// 具有指针类似的行为
		T& operator*()
		
			return *_ptr;
		

		T* operator->()
		
			return _ptr;
		

		T* Get()
		
			return _ptr;
		

		//
		// 用户可能需要获取引用计数
		int use_count()const
		
			return *_pCount;
		

		///
		// 解决浅拷贝方式:采用引用计数
		shared_ptr(const shared_ptr<T>& sp)
			: _ptr(sp._ptr)
			, _pCount(sp._pCount)
			, _pMutex(sp._pMutex)
		
			AddRef();
		

		shared_ptr<T, DF>& operator=(const shared_ptr<T, DF>& sp)
		
			if (this != &sp)
			
				// 在和sp共享之前,this先要将之前的状态清空
				Release();

				// this就可以和sp共享资源以及计数了
				_ptr = sp._ptr;
				_pCount = sp._pCount;
				_pMutex = sp._pMutex;
				AddRef();
			

			return *this;
		

	private:
		void AddRef()
		
			if (nullptr == _ptr)
				return;

			_pMutex->lock();
			++(*_pCount);
			_pMutex->unlock();
		

		void Release()
		
			if (nullptr == _ptr)
				return;

			bool isDelete = false;
			_pMutex->lock();

			if (_ptr && 0 == --(*_pCount))
			
				// delete _ptr;
				DF df;
				df(_ptr);
				delete _pCount;
				_pCount = nullptr;
				isDelete = true;
			

			_pMutex->unlock();

			if (isDelete)
			
				delete _pMutex;
			
		
	private:
		T* _ptr;        // 用来接收资源的
		int* _pCount;   // 指向了引用计数的空间---记录的是使用资源的对象的个数
		mutex* _pMutex; // 目的:保证对引用计数的操作是安全的
	;

c++智能指针(代码片段)

C++四种智能指针为什么要有智能指针1、裸指针中可能存在的问题2、RAII思想(1)RAII原理(2)整个RAlI过程总结为四个步骤:3、C++内存泄漏(1)堆内存泄露(2)系统资源泄露什么是智能... 查看详情

c++智能指针(代码片段)

文章目录智能指针的使用及原理智能指针的使用智能指针的原理C++中的智能指针std::auto_ptrstd::unique_ptrstd::shared_ptrstd::shared_ptr的基本设计std::shared_ptr的线程安全问题std::shared_ptr的定制删除器std::weak_ptrstd::shared_ptr的循环引用... 查看详情

c++智能指针(代码片段)

文章目录智能指针的使用及原理智能指针的使用智能指针的原理C++中的智能指针std::auto_ptrstd::unique_ptrstd::shared_ptrstd::shared_ptr的基本设计std::shared_ptr的线程安全问题std::shared_ptr的定制删除器std::weak_ptrstd::shared_ptr的循环引用... 查看详情

c++中的智能指针(代码片段)

前两天的电话面试中被问到智能指针的概念,完全答不上来。特意回来了解了一下,总结了一些智能指针的用法。之后再接着写C++的多线程。为什么要使用智能指针?一般来说在C++中新建指针都会使用new来新建一个被指向的对象... 查看详情

[c++]智能指针(代码片段)

目录1.为什么需要智能指针?2.RAII(资源获取及初始化)2.1RAII方式的原理2.2重大问题2.3智能指针原理3.C++98auto_ptr(不要用)3.1解决浅拷贝的方式一3.2解决浅拷贝的方式二4.C++11unique_ptr4.1管理的资源多样如何释放4.2模拟实现5.C&... 查看详情

[c++]智能指针(代码片段)

目录1.为什么需要智能指针?2.RAII(资源获取及初始化)2.1RAII方式的原理2.2重大问题2.3智能指针原理3.C++98auto_ptr(不要用)3.1解决浅拷贝的方式一3.2解决浅拷贝的方式二4.C++11unique_ptr4.1管理的资源多样如何释放4.2模拟实现5.C&... 查看详情

这次肯定可以看懂c++智能指针,适合新手小白,c++内功修炼(代码片段)

小白学习C++智能指针智能指针的本质为什么要使用智能指针?智能指针的原理智能指针的类型auto_ptr特点缺陷unique_ptr功能实验share_ptr成员函数实例weak_ptrweak_ptr是什么weak_ptr如何使用weak_ptr模板类提供的成员方法参考资料... 查看详情

c++智能指针及循环引用(代码片段)

目录:auto_ptrunique_ptrshared_ptrweak_ptr循环引用问题智能指针的原理:智能指针的原理:智能指针是一个类,这个类的构造函数中传入一个普通指针,析构函数中释放传入的指针指向的内存。智能指针本身是栈上... 查看详情

c++智能指针讲解及模拟实现(代码片段)

C++四种智能指针为什么要有智能指针1、裸指针中可能存在的问题2、RAII思想(1)RAII原理(2)整个RAlI过程总结为四个步骤:3、C++内存泄漏(1)堆内存泄露(2)系统资源泄露什么是智能... 查看详情

c++智能指针--所有的类型的解析(代码片段)

参考文献:C++智能指针简单剖析C++弱引用智能指针weak_ptr的用处关于shared_ptr与weak_ptr的使用1.智能指针背后的设计思想1.1无智能指针造成内存泄漏的例子voidremodel(std::string&str)std::string*ps=newstd::string(str);//堆内... 查看详情

c++进阶---智能指针(代码片段)

智能指针RAIIC++98的auto_ptrauto_ptr的使用auto_ptr模拟实现C++11的unique_ptr,shared_ptr,weak_ptrunique_ptrunique_ptr的使用unique_ptr的模拟实现shared_ptrshared_ptr的使用shared_ptr的模拟实现线程安全循环引用lambda+shared_p 查看详情

c++进阶---智能指针(代码片段)

智能指针RAIIC++98的auto_ptrauto_ptr的使用auto_ptr模拟实现C++11的unique_ptr,shared_ptr,weak_ptrunique_ptrunique_ptr的使用unique_ptr的模拟实现shared_ptrshared_ptr的使用shared_ptr的模拟实现线程安全循环引用lambda+shared_p 查看详情

c++智能指针的模拟实现(代码片段)

#include<iostream>#include<mutex>usingnamespacestd;//智能指针的模拟//内部就是一个需要管理的空间//构造函数赋值,析构函数清理//指针要实现*,返回解引用后的空间,->返回指针//auto_ptr的核心是拷贝构造,赋值... 查看详情

c++异常机制和智能指针机制的杂谈(代码片段)

...使用异常安全规范异常的优缺点优点缺点异常的多次抛出智能指针一个简单的智能指针智能指针解决异常中内存泄漏的问题RAII智能指针的拷贝问题auto_ptrunique_ptrshared_ptrweak_ptr什么是循环引用删除器异常认识异常C语言中的处理异... 查看详情

c++异常机制和智能指针机制的杂谈(代码片段)

...使用异常安全规范异常的优缺点优点缺点异常的多次抛出智能指针一个简单的智能指针智能指针解决异常中内存泄漏的问题RAII智能指针的拷贝问题auto_ptrunique_ptrshared_ptrweak_ptr什么是循环引用删除器异常认识异常C语言中的处理异... 查看详情

c++编程经验:智能指针--裸指针管得了的我要管,裸指针管不了的我更要管!(代码片段)

文章目录智能指针介绍手写智能指针不带引用计数的智能指针升级手写的智能指针智能指针的循环引用问题强智能指针弱智能指针弱智能指针升级为强智能指针多线程访问共享对象智能指针介绍智能指针是存储指向动态分配࿰... 查看详情

c++编程经验:智能指针--裸指针管得了的我要管,裸指针管不了的我更要管!(代码片段)

文章目录智能指针介绍手写智能指针不带引用计数的智能指针升级手写的智能指针智能指针的循环引用问题强智能指针弱智能指针弱智能指针升级为强智能指针多线程访问共享对象智能指针介绍智能指针是存储指向动态分配࿰... 查看详情

c++智能指针-全部用法详解(代码片段)

为什么要学习智能指针?咳咳,这个问题不是问大家的,是询问我自己的!我依稀记得刚离校出来找实习工作那会,去面试一份工作,其中有一个环节需要答题;有一道题目就是问什么是智能指针?... 查看详情