浅谈智能指针

1、为什么要使用智能指针?

       C++的内存管理是让很多人头疼的事,一不小心就会发生内存泄漏,重复释放,野指针等问题。大部分关于指针的问题都是来源于堆空间,为什么呢?我们知道栈上的空间是由系统维护的,申请和释放的工作都是由系统根据栈的性质来完成的,不需要我们过多干预。而堆上空间的申请(new)和释放(delete)都必须由程序员显示的调用,并且很重要的一点,这段空间的生命周期就在new 和 delete 之间。但是我们不能避免程序还未执行到delete就跳转了,或者在函数中没有执行到delete语句就返回了,如果我们不在每一个可能跳转和返回之前释放内存,就会造成内存泄漏。所以程序员必须很仔细的申请并给出对应的释放语句,但是由于程序的复杂都增大,判断、循环、递归这样的语句会让程序的走向处于不定境地。很有可能出现内存泄露的问题。使用智能指针可以很大程度上的避免这个问题,因为智能指针就是一个类,当超出了类的作用域是,类会自动调用析构函数,析构函数会自动释放资源。

2、什么是RAII?

 RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这样做有两个好处:

            (1)我们不需要再显示的释放资源

            (2)采用这种方式,对象所需的资源在其生命期内始终保持有效

在C++中它被应用的实例除了这里的智能指针,还有C++11中的lock_guard 对互斥锁的管理也是典型的RAII机制。关于RAII模板化实现 ,参考下面的博客链接。

参考:https://blog.csdn.net/10km/article/details/49847271

但是我们要注意RAII != 智能指针,它只是解决问题的一种思想。智能指针顾名思义,它肯定要有指针的行为,因此我们还需要把* 和 -->进行重载。

3、有哪些智能指针?

1、auto_ptr

我们先来看它的简单使用

class Date
{
public:
	Date()
	{
		cout << "Date()" << endl;
	}
	~Date()
	{
		cout << "~Date()" << endl;
	}

	int year;
	int month;
	int days;
};

void  test()
{
	auto_ptr<Date> d1(new Date); 
	auto_ptr<Date> d2(d1);
        //d1->year = 2019;
}

                                                                                                                

分析一下结果发现明明有连个对象却只构造了一次,析构也只有一次,执行test()函数中第一行代码肯定是会调用构造函数,所以结果打印第一行没问题,下一行代码用的d1拷贝构造d2,也没问题,然后出了函数作用域,要销毁对象,按照入栈顺序要先销毁d2,再销毁d1。但只打印了一次,这一次肯定是d2的,d1没有打印就会说明这个对象已经被销毁了。我们如果把上面注释的那一行代码放开,程序运行之后会发生右边这种情况。为什么会这样呢?这是由于auto_ptr的实现原理决定的,auto_ptr的实现原理就是管理权转移。当对象拷贝或者赋值后,前面的对象就悬空了 。下面模拟实现一份auto_ptr

template <class T>
class AutoPtr {
public:
	AutoPtr(T* ptr)
		:_ptr(ptr)
	{
		cout << "AutoPtr() :" <<_ptr << endl;
	}
	~AutoPtr()
	{
		cout << "~AutoPtr() :"  << _ptr<< endl;
		if (_ptr)
			delete _ptr;
	}
	//这里没有用const 因为要修改它
	AutoPtr(AutoPtr<T>& s)
	{
		cout << "AutoPtr(T&ptr) : " << _ptr << endl;
		_ptr = s._ptr;
		//将原对象悬空
		s._ptr = nullptr;
	}
	AutoPtr<T> operator=(AutoPtr<T>& s)
	{
		if (*this != s) {
			//如果当前对象中有资源,释放
			if (_ptr)
				delete _ptr;
			//将s中的资源转移到当前对象中
			_ptr = s._ptr;
			s._ptr = nullptr;
		}
		return *this;
	}
	T& operator* ()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
private:
	T* _ptr;
};
void TestAutoPtr()
{
	AutoPtr<Date> p(new Date);
	AutoPtr<Date> p2(p);
}

简单总结一下就是auto_ptr可用来管理单个对象的内存,控制权可以随意转换但只有一个在用,不要轻易使用operator=() 或者拷贝构造,使用了就不要再访问之前对象的数据。所以说它的一些设计也不太符合C++的编程设计,所以这个指针已经被C++淘汰了。

2、unique_ptr

unique_ptr 是一种简单粗暴的智能指针,既然你拷贝和赋值会造成很多问题,那就不要拷贝了。所以它是独享所有权的,不让你拷贝和赋值。下面是unique_ptr的模拟实现

using namespace std;
template <class T>
class UniquePtr {
public:
	UniquePtr(T* ptr)
		:_ptr(ptr)
	{}
	~UniquePtr()
	{
		if (_ptr)
			delete _ptr;
	}
	//C++防拷贝方式
	UniquePtr(UniquePtr<T> const& ) = delete;
	UniquePtr<T> operator=(UniquePtr<T>const & ) = delete;

	//C++98防拷贝方式 (只声明不定义)不能不写,它会用默认的,所以必须写
	UniquePtr(UniquePtr<T> const&);
	UniquePtr<T>operator=(UniquePtr<T> const &);

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

3、shared_ptr

auto_ptr有缺陷,unique_ptr不能拷贝,如果真的要实现拷贝,C++11中开始提供更靠谱的并且支持拷贝的shared_ptr

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

1. shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。

2. 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。

3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;

4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;

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

	}
	//p(p1)
	SharedPtr(const SharedPtr<T> & s)
		: _ptr(s._ptr)
		,_pCount(s._pCount)
		,_pMutex(s._pMutex)
	{
		AddCount();
	}
	//p = p1;
	SharedPtr<T>& operator=(const SharedPtr<T> & s)
	{
		if (this != &s) {
			if(_ptr)
				//释放管理的旧资源
				Release();
			_ptr = s._ptr;
			_pCount = s._pCount;
			// 如果是一个空指针对象,则不加引用计数,否则才加引用计数
			if(_ptr)
				AddCount();
		}
		return *this;
	}
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return  _ptr;
	}
	int AddCount()
	{
		_pMutex->lock();
		++(*_pCount);
		_pMutex->unlock();
		return *_pCount;
	}
	int SubCount()
	{
		_pMutex->lock();
		--(*_pCount);
		_pMutex->unlock();
		return *_pCount;
	}
	int UseCount()
	{
		return *_pCount;
	}
	T* Get()
	{
		return _ptr;
	}
private:
	void Release()
	{
		if (_ptr && --(*_pCount) == 0) {
			delete _ptr;
			delete _pCount;
		}
	}
private:
	T* _ptr;
	int *_pCount;   //引用计数
	mutex* _pMutex;
};

void TestSharedPtr()
{
	SharedPtr<int>sp1(new int(10));
	SharedPtr<int>sp2(sp1);
	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;

}
int main()
{
	TestSharedPtr();
	getchar();
	return 0;
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值