智能指针的理解

题目

  • 为什么要定义智能指针?
  • 智能指针的原理
    • RALL
  • 智能指针的使用
      • std::auto_ptr
      • std::unique_ptr
      • std::shared_ptr

为什么要定义智能指针?

之前定义指针申请内存空间使用后要进行delete进行资源释放,但是如果在资源释放之前就抛出异常,则不会进行正常的资源释放,造成资源泄露的问题。长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。
例如:

void MemoryLeaks()
{
// 1.内存申请
int* p1 = new int[10];
throw;

delete[] p1;
}

因此通过定义智能指针,利用对象的生命周期来控制程序资源,在生命周期结束时自动释放资源。

智能指针的原理

RALL

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。这种做法有两大好处:

  • 不需要显式地释放资源。
  • 采用这种方式,对象所需的资源在其生命期内始终保持有效。
//智能指针:
//1.实现RALL思想
//2.实现指针功能:*,->
template <class T>
class AutoPtr
{
public:
	//RALL
	//构造函数获取资源的管理权
	AutoPtr(T* ptr)
		:_ptr(ptr)
	{}
	//析构中释放资源
	~AutoPtr()
	{
		if (_ptr)
			delete _ptr;
	}

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

	T&  operator*()
	{
		return *_ptr;
	}

	//管理权转移
	AutoPtr(AutoPtr<T>& ap)
		:_ptr(ap.ptr)
	{
		ap._ptr = nullptr;
	}

	AutoPtr<T>& operator=(AutoPtr<T>& ap)
	{
		if (this != &ap)
		{
			if (_ptr)
				delete _ptr;
			//管理权转移
			_ptr = ap._ptr;
			ap._ptr = nullptr;
		}
		return *this;
	}
private:
	T* _ptr;
};

class A
{
public:
	int  _a = 10;
};

void test()
{
	错误写法:该写法容易造成资源二次释放
	//int* ptr = new int;
	//SmartPtr sp(ptr);
	//SmartPtr sp2(ptr);
	//正确写法:
	AutoPtr<int> sp1(new int);
	//SmartPtr sp2((int*)malloc(sizeof(int)));
	AutoPtr<A> sp2(new A);
	
	//智能指针,编译器调用 析构自动释放内存,不存在内存泄露的问题
	*sp1 = 10;
	sp2->_a = 100;
	(*sp2)._a = 1000;

	//普通指针,手动释放内存,存在内存泄漏的问题
	int* p = new int;
	A* pa = new A;
	*p = 1;
	pa->_a = 5;
	(*pa)._a = 55;
	delete p;
	delete pa;
}

智能指针的使用

std::auto_ptr

C++98版本的库中就提供了auto_ptr的智能指针。
auto_ptr的实现原理:管理权转移的思想。
拷贝时直接将新指针指向被拷贝对象资源,被拷贝对象指针悬空。

	auto_ptr<int> sp1(new int);
	auto_ptr<int> sp2(sp1); // 管理权转移
 	//sp1悬空

std::unique_ptr

unique_ptr的实现原理:简单粗暴的防拷贝。

	//unique_ptr:防拷贝
	unique_ptr<int> up(new int);
	//unique_ptr的拷贝构造为删除函数
	//unique_ptr up2(up);
	unique_ptr<int> up3(new int);
	//unique_ptr赋值运算符为删除函数
	//up3 = up;

std::shared_ptr

C++11中开始提供更靠谱的并且支持拷贝的shared_ptr.

shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。

  1. shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享
  2. 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一
  3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源
  4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。

循环引用分析:

  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
{
	shared_ptr<ListNode> _next;
	shared_ptr<ListNode> _prev;
	int _data;

	~ListNode()
	{
		cout << "~ListNode" << endl;
	}
};
void test1()
{
	shared_ptr<ListNode> n1(new ListNode);
	shared_ptr<ListNode> n2(new ListNode);

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

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

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

使用weak_ptr可以在拷贝时不进行计数+1操作,因此可以正常调用析构,进行资源释放。

struct ListNode
{
	weak_ptr<ListNode> _next;
	weak_ptr<ListNode> _prev;
	int _data;

	~ListNode()
	{
		cout << "~ListNode" << endl;
	}
};
void test1()
{
	shared_ptr<ListNode> n1(new ListNode);
	shared_ptr<ListNode> n2(new ListNode);

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

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

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

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