C++:智能指针(auto_ptr/unique_ptr/shared_ptr/weak_ptr)

为什么需要智能指针?

C++没有垃圾回收机制。

#include<iostream>
using namespace std;

int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");

	return a / b;
}

void Func()
{
	// 1、如果p1这里new 抛异常会如何?
	// 2、如果p2这里new 抛异常会如何?
	// 3、如果div调用这里又会抛异常会如何?
	int* p1 = new int[10];
	int* p2 = nullptr;
	int* p3 = nullptr;

	try
	{
		p2 = new int[20];
	}
	catch (...)
	{
		delete[] p1;
		throw;
	}

	try
	{
		p3 = new int[10];
	}
	catch (...)
	{
		delete[] p1;
		delete[] p2;
		throw;
	}

	try
	{
		cout << div() << endl;
	}
	catch (...)
	{
		delete[] p1;
		delete[] p2;
		delete[] p3;

		throw;
	}

	delete[] p1;
	delete[] p2;
}

int main()
{
	try
	{
		Func();
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}

	return 0;
}

在多次new时有可能抛异常,就需要一层一层捕获异常并释放资源还需要条件判断,非常麻烦,否则抛异常之后delete多了或者少了都不行。一不小心就内存泄露了。

如何避免内存泄漏

1. 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。ps: 这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条智 能指针来管理才有保证。

2. 采用RAII思想或者智能指针来管理资源。

3. 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。

4. 出问题了使用内存泄漏工具检测。ps:不过很多工具都不够靠谱,或者收费昂贵。

智能指针的使用及原理

智能指针指针包含两个部分:RAII(通过构造和析构管理资源)和像指针一样的操作符重载。

智能指针的本质时将资源生命周期与对象绑定

下面来见一见RAII的智能指针:

template<class T>
class smart_ptr
{
public:
	smart_ptr(T* ptr)
	:_ptr(ptr)
	{}

	~smart_ptr()
	{
		delete _ptr;
	}

private:
	T* _ptr;
};

我们再来实现像指针一样的部分:

template<class T>
	class smart_ptr
	{
	public:
		//RAII
		smart_ptr(T* ptr)
			:_ptr(ptr)
		{}

		~smart_ptr()
		{
			std::cout << "delete" << _ptr << std::endl;
			delete _ptr;
		}


		//像指针一样
		T& operator*()
		{
			return *_ptr;
		}

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

		T& operator[](size_t pos)
		{
			return _ptr[pos];
		}

	private:
		T* _ptr;
	};
int main()
{
	szg::smart_ptr<Date> sp1(new Date());

	cout << sp1->_year << ":" << sp1->_month << ":" << sp1->_day << endl;
	++sp1[0]._year;
	++(*sp1)._month;
	++sp1->_day;
	cout << sp1->_year << ":" << sp1->_month << ":" << sp1->_day << endl;

	return 0;
}

//0:0 : 0
//1 : 1 : 1
//delete00732778

问题一:拷贝构造会重复析构

智能指针的方式也会产生一些问题,比如拷贝构造和赋值会重复析构。

int main()
{
	szg::smart_ptr<Date> sp1(new Date());
	szg::smart_ptr<Date> sp2(sp1);
	

	return 0;
}
//delete00CE27C0
//delete00CE27C0
//直接报错

在学习解决这个问题前先了解一下智能指针的发展历史:

// C++智能指针发展历史

// C++98 auto_ptr 资源管理权转移-->对象悬空 很多公司明确要求不能使用它
// boost scoped_ptr 防拷贝
// boost shared_ptr/weak_ptr // 引用计数

// C++11 unique_ptr 防拷贝
// C++11 shared_ptr/weak_ptr // 引用计数

auto_ptr

最先提出的是auto_ptr,它是通过资源管理权转移解决重复析构问题的:

	template<class T>
	class auto_ptr
	{
	public:
		//RAII
		auto_ptr(T* ptr)
			:_ptr(ptr)
		{}

		~auto_ptr()
		{
			if (_ptr)
			{
				std::cout << "delete" << _ptr << std::endl;
				delete _ptr;
			}
		}

		auto_ptr(auto_ptr<T>& ap)
			:_ptr(nullptr)
		{
			std::swap(_ptr, ap._ptr);
		}

		auto_ptr<T>& operator=(auto_ptr<T>& ap) = delete;

		//像指针一样
		T& operator*()
		{
			return *_ptr;
		}

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

		T& operator[](size_t pos)
		{
			return _ptr[pos];
		}

	private:
		T* _ptr;
	};

貌似解决了问题,但会导致对象悬空,不建议使用。

unique_ptr

unique_ptr脱胎于boost库(C++标准库的预备库)的scoped_ptr,通过禁用拷贝的方式解决重复析构问题。

	template<class T>
	class unique_ptr
	{
	public:
		//RAII
		unique_ptr(T* ptr)
			:_ptr(ptr)
		{}

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

		unique_ptr(unique_ptr<T>& ap) = delete;
		unique_ptr<T>& operator=(unique_ptr<T>& ap) = delete;

		//像指针一样
		T& operator*()
		{
			return *_ptr;
		}

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

		T& operator[](size_t pos)
		{
			return _ptr[pos];
		}

	private:
		T* _ptr;
	};

shared_ptr/weak_ptr

shared_ptr/weak_ptr使用引用计数方式防止拷贝构造。

新开辟一块区域作为引用计数的空间:

template<class T>
	class shared_ptr
	{
	public:
		//RAII
		shared_ptr(T* ptr)
			:_ptr(ptr)
			, _pcnt(new int(1))
			,_pmutex(new std::mutex)
		{}

		void release()
		{
			bool flag = false;
			_pmutex->lock();
			if (--(*_pcnt) == 0 && _ptr)
			{
				delete _ptr;
				delete _pcnt;
				flag = true;
			}
			_pmutex->unlock();
			if (flag)
			{
				delete _pmutex;
			}
		}

		~shared_ptr()
		{
			release();
		}

		shared_ptr(const shared_ptr<T>& sp)
		:_ptr(sp._ptr)
		,_pcnt(sp._pcnt)
		,_pmutex(sp._pmutex)
		{
			_pmutex->lock();
			++(*_pcnt);
			_pmutex->unlock();
		}

		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr != sp._ptr)
			{
				release();
				_ptr = sp._ptr;
				_pcnt = sp._pcnt;
				_pmutex = sp._pmutex;
				_pmutex->lock();
				++(*_pcnt);
				_pmutex->unlock();
			}
			return *this;
		}



		//像指针一样
		T& operator*()
		{
			return *_ptr;
		}

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

		T& operator[](size_t pos)
		{
			return _ptr[pos];
		}

	private:
		T* _ptr;
		int* _pcnt;
		std::mutex* _pmutex;
	};

int main()
{
	szg::shared_ptr<int> sp1(new int(0));
	szg::shared_ptr<int> sp2(sp1);
	szg::shared_ptr<int> sp3(sp2);
	(*sp1)++;
	(*sp2)++;

	cout << *sp1 << endl;
	cout << *sp2 << endl;
	sp1 = sp2;

	szg::shared_ptr<int> sp4(new int(10));
	szg::shared_ptr<int> sp5(sp4);
	sp1 = sp4;

	

	return 0;
}

std::shared_ptr的循环引用

循环引用分析:

1. node1和node2两个智能指针对象指向两个节点,引用计数变成1,我们不需要手动 delete。

2. node1的_next指向node2,node2的_prev指向node1,引用计数变成2。

3. node1和node2析构,引用计数减到1,但是_next还指向下一个节点。但是_prev还指向上 一个节点。

4. 也就是说_next析构了,node2就释放了。

5. 也就是说_prev析构了,node1就释放了。

6. 但是_next属于node的成员,node1释放了,_next才会析构,而node1由_prev管理,_prev 属于node2成员,所以这就叫循环引用,谁也不会释放。

 

struct ListNode
{
	ListNode()
	{
		val = 0;
	}
	int val;
	szg::shared_ptr<ListNode> _next;
	szg::shared_ptr<ListNode> _prev;


	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};

void test_shared_ptr2()
{
	szg::shared_ptr<ListNode> n1(new ListNode);
	szg::shared_ptr<ListNode> n2(new ListNode);

	n1->_next = n2;
	n2->_prev = n1;

	cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;
}

int main()
{
	test_shared_ptr2();
	return 0;
}

循环引用的本质是节点内含智能指针,且节点内的智能指针互相引用(指向对方)。

由于计数引用导致了:

因为你中有我,我不能先释放

因为我中有你,你不能先释放

导致都不能释放,直接造成内存泄漏

循环引用是shared_ptr的死穴,要用weak_ptr解决。

weak_ptr

weak_ptr解决循环引用是通过使weak_ptr可以指向资源,但是不增加引用计数实现的。不支持管理资源,只用于share_ptr的拷贝,是shared_ptr的小弟。

	template<class T>
	class weak_ptr
	{
	public:
		weak_ptr()
			:_ptr(nullptr)
		{}

		weak_ptr(const shared_ptr<T>& sp)
			:_ptr(sp.get())
		{}

		weak_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			_ptr = sp.get();
			return *this;
		}

		// 像指针一样
		T& operator*()
		{
			return *_ptr;
		}

		T* operator->()
		{
			return _ptr;
		}
	public:
		T* _ptr;
	};
struct ListNode
{
	ListNode()
	{
		val = 0;
	}
	int val;
	// 可以指向资源/访问资源,不参与资源管理,不增加引用计数
	szg::weak_ptr<ListNode> _next;
	szg::weak_ptr<ListNode> _prev;

	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};

void test_shared_ptr2()
{
	szg::shared_ptr<ListNode> n1(new ListNode);
	szg::shared_ptr<ListNode> n2(new ListNode);

	n1->_next = n2;
	n2->_prev = n1;

	cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;
}

int main()
{
	test_shared_ptr2();
	return 0;
}

定制删除器

定制一个资源释放的方式,毕竟newnew[]出来的资源的释放方式是不一样的,得匹配deletedelete[]

只要使用包装器对象保存释放资源的方法即可:

template<class T>
	class shared_ptr
	{
	public:
		//RAII
		shared_ptr(T* ptr = nullptr, const std::function<void(T*)> func = [](T* t) {delete t;})
			:_ptr(ptr)
			, _pcnt(new int(1))
			,_pmutex(new std::mutex)
		{
			_func = func;
		}

		void release()
		{
			bool flag = false;
			_pmutex->lock();
			if (--(*_pcnt) == 0 && _ptr)
			{
				_func(_ptr);
				delete _pcnt;
				flag = true;
			}
			_pmutex->unlock();
			if (flag)
			{
				std::cout << "delete" << std::endl;
				delete _pmutex;
			}
		}

		~shared_ptr()
		{
			release();
		}

		shared_ptr(const shared_ptr<T>& sp)
		:_ptr(sp._ptr)
		,_pcnt(sp._pcnt)
		,_pmutex(sp._pmutex)
		{
			_func = sp._func;
			_pmutex->lock();
			++(*_pcnt);
			_pmutex->unlock();
		}

		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr != sp._ptr)
			{
				release();
				_ptr = sp._ptr;
				_pcnt = sp._pcnt;
				_pmutex = sp._pmutex;
				_func = sp._func;
				_pmutex->lock();
				++(*_pcnt);
				_pmutex->unlock();
			}
			return *this;
		}

		int use_count()
		{
			_pmutex->lock();
			int ret = (*_pcnt);
			_pmutex->unlock();
			return ret;
		}

		T* get() const
		{
			return _ptr;
		}



		//像指针一样
		T& operator*()
		{
			return *_ptr;
		}

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

		T& operator[](size_t pos)
		{
			return _ptr[pos];
		}

	private:
		T* _ptr;
		int* _pcnt;
		std::mutex* _pmutex;
		std::function<void(T*)> _func;
	};
int main()
{
	szg::shared_ptr<string> sp1(new string[10], [](string* s) {delete[] s;});
	szg::shared_ptr<string> sp2(sp1);
	szg::shared_ptr<string> sp3(sp2);
	(*sp1) = "111111";
	(*sp2) = "222222";

	cout << *sp1 << endl;
	cout << *sp2 << endl;
	sp1 = sp2;

	szg::shared_ptr<string> sp4(new string[10], [](string* s) {delete[] s;});
	szg::shared_ptr<string> sp5(sp4);
	sp1 = sp4;

	

	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JDSZGLLL

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值