C++智能指针

北战南征 提交于 2019-11-27 18:37:09

智能指针的使用和原理

RAII
RAII(resource acquisition is initaliation) 是一种利用对象生命周期来控制程序资源( 内存,文件句柄,网络连接, 互斥量等) 的技术
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源.实际上是把管理资源的任务给给一个对象,此时就可以:

  • 不需要显式的释放资源
  • 采取这种方式,对象所需的资源在其声明周期内始终保持有效

智能指针的原理

//在模板类中重载*,->.使smartptr可以实现指针一样的使用
template<class T>
class SmartPtr {
public:
	SmartPtr(T* ptr = nullptr)
		: _ptr(ptr)
	{}
	~SmartPtr()
	{
		if(_ptr)
		delete _ptr;
	}
	T& operator*() {return *_ptr;}
	T* operator->() {return _ptr;}
private:
	T* _ptr;
};
struct Date
{
	int _year;
	int _month;
	int _day;
};
int main()
{
	SmartPtr<int> sp1(new int);
	*sp1 = 10
	cout<<*sp1<<endl;
	SmartPtr<int> sparray(new Date);
	// 需要注意的是这里应该sparray.operator->()->_year = 2020;
	// 本来应该是sparray->->_year这里语法上为了可读性,省略了一个->
	sparray->_year = 2020;
	sparray->_month = 1;
	sparray->_day = 1;
	}

std::auto_ptr :

//C++库中智能指针都定义在memory中
#include <memory>

class Date
{
public:
	Date() { cout<< "Date()" << endl;}
	~Date(){ cout<< "~Date()" << endl;}
	
	int _year;
	int _month;
	int _day;
}

int main ()
{
	auto_ptr<Date> ap(new Date);
	auto_ptr<Date> copy(ap);
	//注意: auto_ptr此处存在问题,当对象拷贝或者赋值之后,前面对象悬空,程序运行出错
	ap->_year = 2020;
	
	return 0;
}
//因为上述问题,一般情况下不使用auto_ptr

unique_ptr :

int main ()
{
	unique_ptr<Date> up(new Date);
	//unique_ptr的设计思路--防拷贝,即不允许拷贝和赋值
}

简化模拟实现unique_ptr

tmplete <class T>
class Uniqueptr
{
public: 
	Uniqueptr(T *ptr = nullptr)
		:_ptr (ptr)
	{}
	~Uniqueptr()
	{
		if(_ptr)
			delete _ptr;
	}
	T& operator*() {return *_ptr;}
	T& operator-> () {return _ptr;}
private:
	//C++11防拷贝: delete
	Uniqueptr(Uniqueptr<T> const&) = delte;
	Uniqueptr &operator =(Uniqueptr<T> const&) = delete
	//c++98防拷贝方式:只声明不实现 + 声明成私有
	Uniqueptr(Uniqueptr<T> const&);
	Uniqueptr & operator = (Uniqueptr<T> cosnt &)
private:
	T * _ptr;
}

std::shared_ptr :

int main ()
{
	//shared_ptr通过引用计数支持智能指针对象的拷贝
	shared_ptr<Date> sp(new Date);
	shared_ptr<Date> copy(sp);

	cout << "ref count:" <<sp.use_count() << endl;
	cout << "ref count:" << copy.use_count() << endl;
	return 0;
}

//结果为2

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

  1. shared_ptr在内部,给每隔资源都维护着一份计数,用来记录该份资源被几个对象共享
  2. 在对象被销毁时(调用析构函数时),说明不在使用该资源,对象的引用-1
  3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源
  4. 如果不是0,说明还有其他对象使用该资源,不能释放,否则其他对象称为野指针.
//实现简单shared_ptr
#pragma once
#include <thread>
#include <mutex>

using std::mutex;

template <class T>
class SharedPtr
{
public:
	SharedPtr(T* ptr = nullptr)
		: _ptr(ptr)
		, _pRefCount(new int(1))
		, _pMutex(new mutex)
	{}
	~SharedPtr()
	{
		Release();
	}

	SharedPtr(const SharedPtr<T>& sp)
		: _ptr(sp._ptr)
		, _pRefCount(sp._pRefCount)
		, _pMutex(sp._pMutex)
	{
		AddRefConut();
	}

	void AddRefConut()
	{
		//加锁或者使用加1的原子操作
		_pMutex->lock();
		(*_pRefCount);
		_pMutex->unlock();
	}
	//sp1 = sp2
	SharedPtr<T>& operator=(const SharedPtr<T>& sp)
	{
		//if(this != &sp)
		if (_ptr != sp._ptr)
		{
			//释放管理的旧资源
			Release();
			
			//共享管理新资源的对象,并增加引用计数
			_ptr = sp._ptr;
			_pRefCount = sp._pRefCount;
			_pMutex = sp._pMutex;

			AddRefConut();
		}
		return *this;
	}

	T& operator*() { return *_ptr; }
	T* operator->() { return _ptr; }
	int UseCount() { return *_pRefCount; }
	T* Get() { return _ptr; }
private:
	void Release()
	{
		bool deleteflag = false;
		//引用计数减一.如果减值0 释放资源
		_pMutex->lock();

		if ((*_pRefCount == 0))
		{
			delete _ptr;
			delete _pRefCount;
			deleteflag = true;
		}
		_pMutex->unlock();

		if (deleteflag == true)
		{
			delete _pMutex;
		}
	}
private:
	mutex *_pMutex; //互斥锁
	int *_pRefCount; //引用计数
	T* _ptr;//指向管理资源的指针

};

int main()
{
	SharedPtr<int> sp1(new int(10));
	SharedPtr<int> sp2(sp1);
	*sp2 = 20;
	cout << sp1.UseCount() << endl;
	cout << sp2.UseCount() << endl;
	SharedPtr<int> sp3(new int(10));
	sp2 = sp3;
	cout << sp1.UseCount() << endl;
	cout << sp2.UseCount() << endl;
	cout << sp3.UseCount() << endl;
	sp1 = sp3;
	cout << sp1.UseCount() << endl;
	cout << sp2.UseCount() << endl;
	cout << sp3.UseCount() << endl;
	return 0;
}

在这里插入图片描述
std::shared_ptr的线程安全

  1. 智能指针对象中引用计数是多个智能指针共享的,两个线程是智能指针的引用计数同时++或–, 这个操作不是原子的,引用计数原来是1, ++ 了两次,可能还是2. 此时引用计数错乱,会导致资源未释放或者程序崩溃,所以智能指针中引用计数++,–是需要加锁的,即引用计数的操作是线程安全的
  2. 智能指针管理的对象存放在堆上,两个线程同时去访问,可能会导致线程安全问题.

std::shared_ptr的循环引用

struct ListNode
{
	int _data;
	shared_ptr<ListNode> _prev;
	shared_ptr<ListNode> _next;
	~ListNode(){ cout << "~ListNode()" << endl; }
};

int main()
{
	shared_ptr<ListNode> node1(new ListNode);
	shared_ptr<ListNode> node2(new ListNode);
	cout << node1.use_count() << endl;
	cout << node2.use_count() << endl;
	node1->_next = node2;
	node2->_prev = node1;
	cout << node1.use_count() << endl;
	cout << node2.use_count() << endl;

在这里插入图片描述
循环引用分析:

  1. node1和node2两个智能指针对象指向两个节点,引用计数变成1,不需要手动delete
  2. node1的_next指向node2,node2的_prev指向node1,引用计数变为2
  3. node1和node2析构,引用计数减到1,但是_next还指向下一个节点,但是_prev还指向上一个节点
  4. _next析构了,node2释放,_prev析构,node1析构
  5. 但是与此同时,_next属于node1成员, node1释放了,_next才会析构,而node1由_prev管理._prev又属于node2成员
    解决方法:
//在引用计数场景下,把节点中的_prev和_next改为weak_ptr
//此时,当node1->_next = node2;和node2->_prev = node1;时weak_ptr的_next和_prev不会增加
node1和node2的引用计数。
struct ListNode
{
	int _data;
	weak_ptr < ListNode> _prev;
	weak_ptr < ListNode> _next;
	~ListNode(){ cout << "~ListNode()" << endl; }
};

int main()
{
	shared_ptr<ListNode> node1(new ListNode);
	shared_ptr<ListNode> node2(new ListNode);
	cout << node1.use_count() << endl;
	cout << node2.use_count() << endl;
	node1->_next = node2;
	node2->_prev = node1;
	cout << node1.use_count() << endl;
	cout << node2.use_count() << endl;
	
	return 0;
}

在这里插入图片描述

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!