C++RAII内存管理技术


文章目录

  • 一.什么是RAII内存管理技术?
  • 二.智能指针
    • unique_ptr
    • shared_ptr
    • 循环引用问题
    • weak_ptr

一.什么是RAII内存管理技术?

  • C++在引入异常机制后,代码执行流的跳转变得难以预料,如果使用普通的指针进行内存管理,很难避免内存泄漏的问题(执行流跳转导致堆区资源无法被释放)
  • RAII技术指的是利用对象的生命周期来管理内存资源,就堆区内存资源的管理而言,指的就是:将指针封装在类中,在类对象构造时获取堆区资源,当类对象生命周期结束时,通过类对象的析构函数自动完成堆区资源的释放,这样的类对象就是智能指针
    • 智能指针可以有效地避免开发中出现内存泄漏的问题,同时为开发者省去了很多时间和精力

二.智能指针

  • C++11标准库中智能指针主要分为三大类:C++RAII内存管理技术_第1张图片

unique_ptr

  • unique_ptr对象之间不允许进行拷贝,即一个unique_ptr对象管理一块堆区内存资源,是一一对应的关系
  • unique_ptr的简单实现原理:
	//不允许拷贝的智能指针
	//delfunc是堆区资源释放函数,用于调用delete或者delete[]
	template<class T,class delfunc>
	class unique_ptr
	{
	public:
		unique_ptr(T * ptr = nullptr)
			:_ptr(ptr)
		{}

		~unique_ptr()
		{
			if (_ptr)
			{
				delfunc del;
				del(_ptr);
				std::cout << "delete" << std::endl;
			}
		}


		//让智能指针可以像指针一样被使用
		T& operator*()
		{
			return *_ptr;
		}

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

		//禁止unique_ptr对象间的拷贝
		unique_ptr(const unique_ptr<T, delfunc>& unptr) = delete;
		unique_ptr<T, delfunc>& operator=(const unique_ptr<T, delfunc>& unptr) = delete;
	private:
		T* _ptr;
	};
  • 使用指针管理堆区资源时,我们会让其指向单个对象或者对象数组,单个对象delete完成资源释放,而对象数组要采用delete[]完成资源释放.unique_ptr的类模板参数delfunc的设计目的就是为了区分上述两种情况,让使用者通过设计仿函数的方式在deletedelete[]之间做选择

shared_ptr

  • shared_ptr是允许进行拷贝的智能指针,然而shared_ptr对象之间发生拷贝时,就会出现多个智能指针对象同时管理同一块堆区内存的情况,shared_ptr通过引用计数的技术避免了同一块堆区资源被释放多次的情况出现:C++RAII内存管理技术_第2张图片
  • 引用计数的实现思路:
    • shared_ptr内部封装一个指针int * recount用来维护引用计数变量
    • 每当一个shared_ptr对象进行构造并申请堆区资源时,同时在堆区申请一个int变量作为该shared_ptr对象所指向的堆区资源的引用计数变量
    • 每当shared_ptr被拷贝时,连同int * recount一起拷贝,然后让引用计数变量加一即可(赋值重载的实现还要考虑引用计数变量减一和堆区资源释放的问题)
    • 每当shared_ptr析构时,引用计数变量减一,若引用计数变量减为0,则释放堆区资源C++RAII内存管理技术_第3张图片
  • shared_ptr的简单实现原理:
    //允许拷贝的智能指针,利用引用计数解决内存管理冲突
	//delfunc是堆区资源释放函数,用于调用delete或者delete[]
	template<class T, class delfunc>
	class shared_ptr
	{
	public:
		//构造智能指针(同时创建引用计数变量)
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr),
			 _recount(new int(1))
		{}
		shared_ptr(const shared_ptr<T, delfunc>& shptr)
			: _ptr(shptr._ptr),
			  _recount(shptr._recount)
		{
			//智能指针拷贝增加引用计数
			(*_recount)++;
		}
		shared_ptr<T, delfunc>& operator=(const shared_ptr<T, delfunc>& shptr)
		{
			if (_ptr != shptr._ptr)
			{
				//智能指针指向改变,其先前指向的堆区内存和引用计数变量需要处理
				Release();
				//完成智能指针拷贝并增加引用计数
				_ptr = shptr._ptr;
				_recount = shptr._recount;
				++(*_recount);
			}
			return (*this);
		}
		~shared_ptr()
		{
			Release();
		}


		//让智能指针可以像指针一样被使用
		T& operator*()
		{
			return *_ptr;
		}

		T* operator->()
		{
			return _ptr;
		}
		T* getptr() const
		{
			return _ptr;
		}
	private:
		//将资源释放函数进行封装,方便复用
		//引用计数减一,若引用计数减为0则释放资源
		void Release()
		{
			//引用计数减一
			--(*_recount);
			//引用计数为0则释放资源
			if (*_recount == 0)
			{
				if (_ptr)
				{
					delfunc del;
					del(_ptr);
					std::cout << "delete" << std::endl;
				}
				//同时注意释放引用计数变量
				delete _recount;
			}
		}
	private:
		T* _ptr;
		int* _recount;
	};
  • shared_ptr类内部同时还要解决引用计数变量带来的线程安全的问题,这里暂不讨论

循环引用问题

  • shared_ptr用在自引用结构体中会出现对象无法析构的问题:

template<class T>
class deletefunc
{
public:
	void operator()(T* ptr)
	{
		delete  ptr;
	}
};
// 循环引用
struct ListNode
{
	int _val;
	shared_ptr<ListNode,deletefunc<ListNode>> _next;
	shared_ptr<ListNode,deletefunc<ListNode>> _prev;
	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};

// 循环引用
void test_shared_cycle()
{
	share_ptr<ListNode, deletefunc<ListNode>> n1(new ListNode);
	share_ptr<ListNode, deletefunc<ListNode>> n2(new ListNode);
	n1->_next = n2;
	n2->_prev = n1;
}
  • test_shared_cycle()函数执行完后,两个链表节点都无法正常析构,shared_ptr引发了循环引用的问题:C++RAII内存管理技术_第4张图片
  • n1节点的析构依赖于n2节点中_prev指针的析构,n2节点中_prev指针的析构依赖于n2节点的析构,n2节点的析构又依赖于n1节点中_next指针的析构, n1节点中_next指针的析构又依赖于n1节点的析构.如此往复构成了循环的依赖关系导致两个节点都无法被释放.
  • weak_ptr就是为了解决循环引用问题而设计的

weak_ptr

  • weak_ptr智能指针不参与堆区内存的申请和释放,也不会增加特定内存块的引用计数(其功能和普通指针差不多),weak_ptr支持以shared_ptr为引用形参的拷贝构造和赋值
  • weak_ptr简单实现原理:
	//用于配合share_ptr的使用用于循环引用的场景
	//delfunc是堆区资源释放函数,用于调用delete或者delete[]
	//weak_ptr不参与资源的申请和释放
	template<class T, class delfunc>
	class weak_ptr
	{
	public:
		weak_ptr()
			:_ptr(nullptr)
		{}

		~weak_ptr()
		{}
		
		weak_ptr(const weak_ptr<T, delfunc>& wptr)
			:_ptr(wptr._ptr)
		{}
		weak_ptr(const share_ptr<T, delfunc>& shptr)
			:_ptr(shptr.getptr())
		{}
		weak_ptr<T, delfunc>& operator=(const weak_ptr<T, delfunc>& wptr)
		{
			_ptr = wptr._ptr;
			return (*this);
		}
		weak_ptr<T, delfunc>& operator=(const share_ptr<T, delfunc>& shptr)
		{
			_ptr = shptr.getptr();
			return (*this);
		}

		//让智能指针可以像指针一样被使用
		T& operator*()
		{
			return *_ptr;
		}

		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _ptr;
	};
  • 自引用结构体中采用weak_ptr就可以避免出现循环引用的问题
    C++RAII内存管理技术_第5张图片

你可能感兴趣的:(c++)