018-智能指针

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了018-智能指针相关的知识,希望对你有一定的参考价值。

参考技术A ◼ 传统指针存在的问题

作用域

智能指针不要去指向栈空间的对象,否则易导致两次析构,智能指针设计是用来指向堆空间的对象的,如下两次析构,第一次是作用域内person指向栈空间的对象析构,第二次是智能指针指向的person对象的栈空间析构

此时如何通过智能指针p调用person对象中的方法呢?p->run();直接调用会报错。
通过运算符重载,重载运算符的功能,用指针运算符->时返回Person *的指针m_obj,即p->run()相当于m_obj->run();

智能指针是对传统指针的再次封装,构造时传入对象,里面有一个传统指针m_obj指向传入的对象内存地址,析构时,智能指针p释放掉,传统指针指向的对象内存也释放delete m_obj
调用方法时,通过运算符重载,返回指向对象内存地址的传统指针
智能指针是一个对象,重载了指针运算符后返回一个指针

传入数组申请了创建了10个对象,但仅仅只析构了一次

◼ shared_ptr的设计理念 多个shared_ptr可以指向同一个对象,当最后一个shared_ptr在作用域范围内结束时,对象才会被自动释放

◼ 可以通过一个已存在的智能指针初始化一个新的智能指针

◼一个shared_ptr会对一个对象产生强引用(strong reference)
◼ 每个对象都有个与之对应的强引用计数,记录着当前对象被多少个shared_ptr强引用着
可以通过shared_ptr的use_count函数获得强引用计数
◼ 当有一个新的shared_ptr指向对象时,对象的强引用计数就会+1
◼ 当有一个shared_ptr销毁时(比如作用域结束),对象的强引用计数就会-1
◼ 当一个对象的强引用计数为0时(没有任何shared_ptr指向对象时),对象就会自动销毁(析构)

指向对象的智能指针销毁一个,use_count就会减一

IV.shared_ptr易出现的问题,两次析构,程序崩溃

V.shared_ptr循环引用的问题:相互持有,始终无法销毁

◼ weak_ptr会对一个对象产生弱引用
◼ weak_ptr可以指向对象解决shared_ptr的循环引用问题

若是使用普通指针,自己会写delete,不存在循环引用的问题,循环引用只是针对智能指针引起的

◼ unique_ptr也会对一个对象产生强引用,它可以确保同一时间只有1个指针指向对象
◼ 当unique_ptr销毁时(作用域结束时),其指向的对象也就自动销毁了
◼ 可以使用std::move函数转移unique_ptr的所有权

不能多个unique_ptr指向同一个对象,会报错

std::move转移智能指针的指向,p1指向person对象,后面转移给p0指向person对象

智能指针11

智能指针

智能指针存在的必要性

  1. malloc出来的空间,没有进行释放,存在内存泄漏的问题。
  2. 异常安全问题。如果在malloc和free之间如果存在抛异常,那么还是有内存 泄漏。这种问题就叫异常安全

智能指针的使用及原理

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:
不需要显式地释放资源。
采用这种方式,对象所需的资源在其生命期内始终保持有效。

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

3. 解决浅拷贝的问题

C++98中的智能指针

auto_ptr:
edition1:
如何解决浅拷贝问题:资源转移。
缺点:转移资源,若此时想对原对象操作会出错。

namespace bit

	template<class T>
	class auto_ptr
	
      public:
	 auto_ptr(T* ptr=nullptr)
		 :_ptr(ptr)
	 
	 auto_ptr(auto_ptr<T>& pt):_ptr(pt._ptr)
	 
		 pt._ptr = nullptr;
	 
	 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;
	 
	 ~auto_ptr()
	 
		 if (_ptr)
		 
			 delete _ptr;
			 _ptr = nullptr;
		 
	 
	 T& operator*()
	 
		 return *_ptr;
	 
	 T* operator->()
	 
		 return _ptr;
	 
	 T* Get()
	 
		 return _ptr;
	 
	private:
		T* _ptr;
	;
;

editio2:转移释放权限

namespace bit

	template<class T>
	class auto_ptr
	
	public:
		auto_ptr(T* ptr =nullptr)
			:_ptr(ptr)
			,owner(false)
		
			if (_ptr)
			
				owner = true;
			
		
		auto_ptr(auto_ptr<T>& t)
			:_ptr(t._ptr)
			,owner(t.owner)
		
			t.owner = false;
		
		auto_ptr<T>& operator=(auto_ptr<T>& t)
		
			if (this != &t)
			
				if (this->_ptr && this->owner)
				
					delete _ptr;
				
				_ptr = t._ptr;
				owner = t.owner;
				t.owner = false;
			
			return *this;
		
		~auto_ptr()
		
			if (_ptr && owner)
			
				delete _ptr;
				owner = false;
				_ptr = nullptr;
			
		
		T& operator*()
		
			return *_ptr;
		
		T* operator->()
		
			return _ptr;
		
		T* Get()
		
			return _ptr;
		

	private:
		T* _ptr;
		bool owner;
	;

void TestAutoPtr()

	bit::auto_ptr<int> ap1(new int);
	*ap1 = 100;

	bit::auto_ptr<int> ap2(ap1);

	// 和我们对指针常规的认知有区别的
	int* p1 = new int;
	int* p2(p1);
	*p1 = 10;
	*p2 = 20;
	delete p1;
	p1 = p2 = nullptr;


	// auto_ptr采用资源转移的方式虽然将浅拷贝的问题解决了,但是引用了新的问题
	if (ap2.Get())
		*ap2 = 2000;
	if (ap1.Get())
		*ap1 = 1000;   // 代码会崩溃,因为ap1当中的资源已经转移走了,ap1当中的指针指向的空


	bit::auto_ptr<int> ap3(new int);
	*ap3 = 300;

	bit::auto_ptr<int> ap4(new int);
	*ap4 = 400;

	ap3 = ap4;

	//
	// 致命的缺陷---可以会导致野指针
	if (true)
	
		bit::auto_ptr<int> ap5(ap2);
		*ap5 = 100;
		*ap2 = 200;
		*ap1 = 300;

		// 再来开if的作用域时,ap5已经将管理的资源释放掉了
		// 而ap1和ap2根本就不知道,其内部的指针称为野指针了
	

	// 如果通过ap1和ap2再访问资源时,代码就会出问题
	*ap2 = 10;


int main()

	TestAutoPtr();
	return 0;

缺陷:可能会导致野指针的情况,比如上面的示例
所以:建议不要使用auto_ptr

C++11中的智能指针

C++11中unique_ptr指针

这个智能指针的作用就是防拷贝,既然拷贝可能会带来资源重复释放,资源泄露的问题,那我从根源上就不让你拷贝。

// unique_ptr: RAII + operator*()/operator->()+解决浅拷贝方式:禁止拷贝---资源独占
// 一份资源只能被一个对象来进行管理,对象之间不能共享资源

// 应用场景:只能应用与资源被一个对象管理 并且不会被共享的场景当中

// 缺陷:多个对象之间不能共享资源

// 负责释放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 bite

	// 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);
			
		

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

		T* operator->()
		
			return _ptr;
		

		T* Get()
		
			return _ptr;
		

		
		// 解决浅拷贝方式---资源独占,即防止被拷贝

		// C++98
		/*
		   将拷贝构造函数以及赋值运算符重载方法只声明不定义,并且需要将其权限设置为私有的

		   // 只声明不定义,没有将权限设置为private---不行的:因为方法别人可以再类外定义
		*/
	private:
		unique_ptr(const unique_ptr<T, DF>&);
		unique_ptr<T, DF>& operator=(const unique_ptr<T, DF>&);

		// 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:
		T* _ptr;
	;

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



#include <memory>

void TestUniquePtr()

	bite::unique_ptr<int> up1(new int);
	bite::unique_ptr<int, Free<int>> up2((int*)malloc(sizeof(int)));
	bite::unique_ptr<FILE, FClose> up3(fopen("12345.txt", "w"));


int main()

	TestUniquePtr();

	bite::unique_ptr<int> up1(new int);
	// bite::unique_ptr<int> up2(up1);

	bite::unique_ptr<int> up3(new int);

	// up1 = up3;

	///
	// 标准库
	unique_ptr<int> up4(new int);
	// unique_ptr<int> up5(up4);
	unique_ptr<int> up6(new int);
	// up4 = up6;
	return 0;

C’++11中的shared_ptr

// 负责释放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;
		
	
;


#include <mutex>

namespace bite

	// 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; // 目的:保证对引用计数的操作是安全的
	;



void TestSharedPtr()

	bite::shared_ptr<int> sp1(new int);
	bite::shared_ptr<int> sp2(sp1);

	bite::shared_ptr<int> sp3(new int);
	bite::shared_ptr<int> sp4(sp3);

	sp3 = sp2;   // sp2和sp3共享同一份资源了

	sp4 = sp2;   // sp2和sp4共享同一份资源了



// 这个语法也是C++11新的语法:在定义成员变量时,可以就地初始化
struct A

	int a = 0;
	int b = 0;
	int c = 0;
;


// 线程函数
void ThreadFunc(bite::shared_ptr<A>& sp, int n)

	for (int i = 0; i < n; ++i)
	
		bite::shared_ptr<A> copy(sp);
		copy->a++;
		copy->b++;
		copy->c++;
	



#include <thread>

void TestSharedPtrSafe()

	bite::shared_ptr<A> sp(new A);

	// t1对象将来就和对应的线程绑定一起了
	thread t1(ThreadFunc, sp, 10000);
	thread t2(ThreadFunc, sp, 10000);

	// 线程等待
	t1.join();
	t2.join();


	// 1. 代码会不会崩溃----->shared_ptr是否安全---没有崩溃:
	// 2. A 对象中的各个成员的值是不是都是20000
	cout << sp->a << endl;
	cout << sp->b << endl;
	cout << sp->c << endl;




/
// 利用标准库中的shared_ptr来进行测试
#include <memory>
void ThreadFuncstd(shared_ptr<A>& sp, int n)

	for (int i = 0; i < nC++编程经验:智能指针 -- 裸指针管得了的我要管,裸指针管不了的我更要管!

C++编程经验:智能指针 -- 裸指针管得了的我要管,裸指针管不了的我更要管!

Qt 智能指针学习(7种QT智能指针和4种std智能指针)

智能指针 与 oc中的指针

C11新特性之智能指针

C++|深入理解智能指针