智能指针11

本文探讨了智能指针在C++中的重要性,特别是auto_ptr、unique_ptr和shared_ptr的原理、使用场景及其在避免内存泄漏和异常安全方面的优势。着重讲解了C++11中unique_ptr和shared_ptr的区别,以及如何解决shared_ptr循环引用引发的内存问题。
摘要由CSDN通过智能技术生成

智能指针存在的必要性

  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 < n; ++i)
	{
		shared_ptr<A> copy(sp);
		copy->a++;
		copy->b++;
		copy->c++;
	}
}

void TestSharedPtrSafestd()
{
	shared_ptr<A> sp(new A);

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

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


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

int main()
{
	TestSharedPtrSafestd();

	shared_ptr<int>  sp(new int);

	TestSharedPtrSafe();

	TestSharedPtr();
	return 0;
}

在这里插入图片描述
shared_ptr:只保证的是自身引用计数的安全,不保证用户在多线程下的线程安全。所以为了保证安全的话,用户得使用atomic(C++11中的)或者加锁的方式。

shared_ptr循环引用所引发的问题。

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
}

int main()
{

	// weak_ptr<int> wp(new int);
	TestLoopRef();
}

在这里插入图片描述
资源泄露的问题。

如何解决shared_ptr所造成的循环调用所引起的内存泄露的问题

在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值