【C++】智能指针

为什么需要智能指针
  • malloc出来没有free,出现内存泄漏;
  • 在free之前抛出异常,没有free,出现内存泄漏,这是异常安全的问题。
智能指针的使用与原理

RAII

RAII是一种利用对象生命周期来控制程序资源的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终有效,最后在对象析构时释放资源。
相当于把管理资源的责任委托给了一个对象,有两大好处:

  • 不需要显示的释放资源;
  • 采用这种方式,对象所需的资源在其生命周期内始终有效。
template<class T>
class SmartPtr
{
	public:
	SmartPtr(T* ptr = nullptr)
			:_ptr(ptr)
	{};
	~SmartPtr()
	{
		if(_ptr)
		{
			delete _ptr;
		}
	}
	private:
		T* _ptr;
};
void MergeSort(int* a,int n)
{
	int* tmp = (int*)malloc(sizeof(int)*n);
	SmartPtr<int> sp(tmp);
	vector<int> v(100000000,20);
}

int main()
{
	try{
		int a[5] = {4,1,2,3,5};
		MergeSort(a,5);
	}
	catch(const execption& e)
	{
		cout << e.what() <<endl;
	}
	return 0;
}

原理
上述不能称之为智能指针,他还不具备指针的行为,指针可以解引用,可以通过“->”指向所指内容,所以要将“*”、“->”重载。

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> sp(new int);
	*sp = 10;
	cout << *sp << endl;
	SmartPtr<int> sd(new Date);
	sd->year = 2019;
	sd->month = 8;
	sd->day = 13;
}

std::auto_ptr
C++库中提供了auto_ptr的智能指针

#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);
	ap->_year = 2019;
	return 0;
}

缺点:当对象拷贝或者赋值后,前面的对象就悬空了。
实现原理:管理权转移的思想。
std::unique_ptr
C++库中提供了更靠谱的unique_ptr指针

int main()
{
	unique_ptr<Date> up(new Date);
	unique_ptr<Date> copy(new Date);
	return 0;
}

设计思路是防拷贝,不让拷贝与赋值。
std::shared_ptr

int main()
{
	shared_ptr<Date> sp(new Date);
	shared_ptr<Date> copy(sp);
	cout << "count" << sp.use_count() <<endl;
	cout << "count" << copy.use_count() <<endl;
}

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

  • shared_ptr在其内部,给每一份资源都维护了一份计数,用来记录该份资源被几个对象共享;
  • 在对象被销毁时,计数减1;
  • 如果计数为0,要释放该资源;
  • 如果不为0,说明还有对象使用该资源,若释放其他对象将指向野指针。
shared_ptr的线程安全问题
  • 需要用锁,shared_ptr中计数++,不是原子性的,可能导致计数错乱,会导致资源未释放或程序崩溃的问题。
  • 智能指针管理的对象存放在堆上,两个线程同时访问,易导致线程安全问题。
#include <thread>
#include <mutex>
#include <iostream>
using namespace std;
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)
	{
		AddRefCount();
	}

	SharedPtr<T>& operator= (const SharedPtr<T>& sp)
	{
		if (_ptr != sp._ptr)
		{
			Release();
			_ptr = sp._ptr;
			_pRefCount = sp._pRefCount;
			_pMutex = sp._pMutex;
			AddRefCount();
		}
		return *this;
	}
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
	int UseCount()
	{
		return *_pRefCount;
	}
	T* Get()
	{
		return _ptr;
	}
	void AddRefCount()
	{
		_pMutex->lock();
		++(*_pRefCount);
		_pMutex->unlock();
	}
private:
	void Release()
	{
		bool flag = false;
		_pMutex->lock();
		if (--(*_pRefCount) == 0)
		{
			delete _ptr;
			delete _pRefCount;
			flag = true;
		}
		_pMutex->unlock();
		if (flag == true)
		{
			delete _pMutex;
		}
	}

private:
	T* _ptr;
	int* _pRefCount;
	mutex* _pMutex;
};
struct Date
{
	int _year;
	int _month;
	int _day;
};

//将AddRefCount与SubRefCount去掉;
//线程安全是偶然性出现的,n越大出现的概率越大;
void SharePtrFunc(SharedPtr<Date>& sp, int n)
{
	cout << sp.Get() << endl;
	for (size_t i = 0; i < n; i++)
	{
		SharedPtr<Date> copy(sp);
		copy->_year++;
		copy->_month++;
		copy->_day++;
	}
}
int main()
{
	SharedPtr<Date> p(new Date);
	cout << p.Get() << endl;
	const size_t n = 100000;
	thread t1(SharePtrFunc, p, n);
	thread t2(SharePtrFunc, p, n);
	t1.join();
	t2.join();
	cout << p->_year << endl;
	cout << p->_month << endl;
	cout << p->_day << endl;

	system("pause");
	return 0;
}

std::shared_ptr循环引用

#include <iostream>
#include <thread>
#include <memory>
using namespace std;
class ListNode
{
public:
	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;
	system("pause");
	return 0;
}

循环引用:
node1和node2两个智能指针对象指向两个节点,计数为1,不需要手动释放。;
node1的_next指向node2,node2的_prev指向node1,计数为2;
node1与node2析构,计数减为1,但_next与_prev分别指向下一个节点与上一个节点不变;即_next析构了,node2就释放了;_prev析构,node1就释放了;
_next属于node1成员,node1释放了_next才会析构,而node1由_prev管理,_prev属于node2成员,这就叫循环引用。
解决方案
在引用计数的场景中,把节点中的_prev与_next改为weak_ptr
原理:node1->_next = node2;node2->_prev = node1时weak_ptr不会增加node1与node2的引用计数。

#include <iostream>
#include <memory>
using namespace std;
class ListNode
{
public:
	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;
	system("pause");
	return 0;
}

若不是new出来的对象,如何通过智能指针管理呢?
shared_ptr中设计了一个删除器。

#include <iostream>
#include <memory>
using namespace std;
template<class T>
struct FreeFunc
{
	void operator()(T* ptr)
	{
		cout << "free" << ptr << endl;
		free(ptr);
	}
};
template<class T>
struct DeleteArrayFunc
{
	void operator()(T* ptr)
	{
		cout << "delete[]" << ptr << endl;
		delete[] ptr;
	}
};

int main()
{
	FreeFunc<int> freefunc;
	shared_ptr<int> sp1((int*)malloc(4), freefunc);
	DeleteArrayFunc<int> deletearrayfunc;
	shared_ptr<int> sp2((int*)malloc(4), deletearrayfunc);
	system("pause");
	return 0;
}

RAII的其他作用
1.设计智能指针
2.设计守卫锁
3.防止异常安全导致的死锁问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值