C++--智能指针--1123

1.智能指针解决的问题

int div()
{
     int a, b;
     cin >> a >> b;
     if (b == 0)
     throw invalid_argument("除0错误");
     return a / b;
}
void Func()
{
// 1、如果p1这里new 抛异常会导致p1不会Delete而导致内存泄漏
// 2、如果p2这里new 抛异常会导致p1和p2都不会delete而导致内存泄漏
// 3、如果div调用这里又会抛异常会导致p1和p2都不会delete而导致内存泄漏
     int* p1 = new int;
     int* p2 = new int;
     cout << div() << endl;
     delete p1;
     delete p2;
}
int main()
{
 try
 {
 Func();
 }
 catch (exception& e)
 {
     cout << e.what() << endl;
 }
 return 0;
}

综上,我们需要一种可以自动回收内存空间的指针。如果我们把指针套在一种类内,类在析构时会带着指针申请的空间一起析构掉。

class A
{
public:
	~A()
	{
		cout << "~A()" << endl;
	}
//private:
	int _a1 = 0;
	int _a2 = 0;
};

2.智能指针的使用及原理

2.1RAII

RAII(Resource Acquisition Is Initialization)直译:在初始化的时候获取资源。即在对象构造的时候获取资源,在对象的生命周期之中,控制对资源的访问,最后在对象析构的时候释放资源。

好处:

  • 不用显示的释放资源(delete)。
  • 对象所需的资源,在对象生命周期之内始终有效。

 模拟实现1

template<class T>
class SmartPtr {
public:
	SmartPtr(T* ptr = nullptr)
		: _ptr(ptr)
	{}

	~SmartPtr()
	{
		if (_ptr)
		{
			cout << "Delete:" << _ptr << endl;
			delete _ptr;
		}
	}

private:
	T* _ptr;
};

 2.2 原理

上述还不能将其称为智能指针,因为它还不具有指针的行为。指针可以解引用,也可 以通过->去访问所指空间中的内容,因此:模板类中还得需要将* 、->重载下,才可让其 像指针一样去使用。 

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

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

 智能指针的思想比较简单,但如何拷贝构造是个问题

 

 这里程序崩溃了 原因依然是之前的老问题,对同一块空间进行了两次析构。(别试,崩溃了)

那我们要跟之前一样实现深拷贝吗?不能,因为我们要的就是浅拷贝,要的就是用新的指针来指向我这块资源。

下面我们来看一下C++是如何对这里进行处理。

2.3 auto_ptr

auto_ptr<int>sp2(sp1)

C98里面的大槽点,对拷贝构造的处理沿用了右值引用的资源转移。(可右值本来就是将亡值,给了就给了。左值可不是啊,如果我还需要对sp1进行访问,那就会报访问空指针的错误)。我们将auto_ptr模拟实现一下

namespace chy
{
	template<class T>
	class auto_ptr
	{
	public:
		//用指针来构造的
		auto_ptr(T* ptr = nullptr)
			:_ptr(ptr)
		{}
		//C98中的auto_ptr对于拷贝的问题处理有问题,相当于右值的资源转移
		//会把ap里面的资源转移给构造出来的智能指针,而ap将不在管理这些资源(置空)
		//所以如果我们再次对与于ap进行解引用操作,将会出现访问空指针的问题
		auto_ptr(auto_ptr<T>& ap)
			:_ptr(ap._ptr)
		{
			ap._ptr = nullptr;
		}
		auto_ptr<T>& operator=(const auto_ptr<T>& ap)
		{
			if (this != &ap)//如果不是自己给自己赋值
			{
				if (_ptr)//原本的资源不管了(删除)
				{
					cout << "Delete" << _ptr << endl;
					delete _ptr;
				}
				_ptr = ap._ptr;
				ap._ptr = nullptr;
			}
			return *this;
		}
		~auto_ptr()
		{
			if (_ptr)
			{
				cout << "Delete:" << _ptr << endl;
				delete _ptr;
			}
		}
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;//  &(*_ptr)
		}
	private:
		T* _ptr;
	};

}

 拷贝是没问题的 但是不能对sp1再进行解引用操作了

 3. C++11的智能指针

 3.1 unique_ptr

遇到问题,逃避问题

不让拷贝

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

template<class T>
	class unique_ptr
	{
	public:
		unique_ptr(T* ptr=nullptr)
			:_ptr(ptr)
		{}
		unique_ptr(const unique_ptr<T>& ap) = delete;
		unique_ptr<T>& operator=(unique_ptr<T>& ap) = delete;

		~unique_ptr()
		{
			if (_ptr)
			{
				cout << "Delete:" << _ptr << endl;
				delete _ptr;
			}
		}
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _ptr;
	};

3.2 share_ptr

引用计数,每有一个指针指向我这块区域,引用计数++,当最后一个指向改位置的智能指针析构时,带着区域一起走。

我这个计数怎么设置?

{
    //..
private:
    static int count;
    //..
}
template<class T>
int shared_ptr<T>::_count = 0;

这样设计可以解决多个指针指向同一块空间的问题。可如果我有两块空间呢?这两块空间的引用计数是不是就变成了同一个了?

所以我们不能纳入静态成员变量,同时我们注意到,我们每指向一块空间,就需要一个单独的计数。那我们在指向空间的时候构造计数不就好了?

而且,由于多个指针共享一个计数,理所应当的成为临界资源,在访问修改时需要加锁!

于是我们在构造函数中下笔

{   //...
	shared_ptr(T* sp = nullptr)
		: _ptr(sp)			
        , _pCount(new int(1))
        ,_pmtx(new std::mutex)
	{}
private:
    T* _ptr;
    int* _pCount;
    std::mutex* _pmtx;
}

整体实现

template<class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* sp=nullptr)
			:_ptr(sp)
			,_pCount(new int(1))
			,_pmtx(new std::mutex)
		{}

		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			,_pCount(sp._pCount)
			,_pmtx(sp._pmtx)
		{
			AddCount();
		}
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (this == &sp) return *this;
			Release();
			_ptr = sp._ptr;
			_pCount = sp._pCount;
			_pmtx = sp._pmtx;
			AddCount();
			return *this;
		}
		void AddCount()
		{
			_pmtx->lock();
			++(*_pCount);
			_pmtx->unlock();
		}
		void Release()
		{
			_pmtx->lock();
			bool flag = false;

			if (--(*_pCount) == 0 && _ptr)
			{
				cout << "Delete: " << _ptr << endl;
				delete _ptr;
				delete _pCount;
				flag = true;
			}
			_pmtx->unlock();
			if (flag) delete _pmtx;
			
		}
		~shared_ptr()
		{
			Release();
		}

		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
		//这个接口是给weak_ptr准备的 用来拿到指针
		T* get() const
		{
			return _ptr;
		}
	private:
		T* _ptr;
		int* _pCount = 0;
		std::mutex* _pmtx;
	};

 3.2.2 shared_ptr的循环引用问题

 

 函数结束时,n2先析构,然后n1析构。

_next管着右边的节点。 _prev管着左边的节点

_next析构,右边节点就delete。 _prev析构,左边节点就delete

那_next什么时候析构呢?左边的节点被delete,调用析构函数,其成员函数_next就析构了。

那左边的节点什么时候被delete呢? _prev析构的时候,左边的节点析构。

那_prev什么时候析构呢?右边的节点被delete,调用析构函数,其成员函数_prev就析构了。

那左边的节点什么时候被delete呢?........(开始套娃)

3.3 weak_ptr

weak_ptr不是常规的智能指针,没有RAII,不支持直接管理资源。weak_ptr主要用shared_ptr构造,用来解决shared_ptr循环引用问题。

在shared_ptr中新增一个函数,用来返回指针

    template<class T>
	class shared_ptr
	{
	public:
	
    //这个接口是给weak_ptr准备的 用来拿到指针
		T* get() const
		{
			return _ptr;
		}
	private:
		T* _ptr;
		int* _pCount = 0;
		std::mutex* _pmtx;
	};

weak_ptr的简单实现

template<class T>
	class weak_ptr
	{
	public:
		weak_ptr()
			:_ptr(nullptr)
		{}
		weak_ptr(const shared_ptr<T>& sp)
			:_ptr(sp.get())
		{}
		weak_ptr(const weak_ptr<T>& wp)
			:_ptr(wp._ptr)
		{}
		weak_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			_ptr = sp.get();
			return *this;
		}

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

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


	private:
		T* _ptr;
	};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值