【c++修行之路】智能指针

前言

大家好久不见,今天来学习有关智能指针的内容~

为什么用智能指针

假如我们有如下场景:

double Div()
{
    
    
	int x, y;
	cin >> x >> y;

	if (y == 0)
		throw "div Exception cause div 0!!!";
	else
		return (double)x / (double)y;
}

int main()
{
    
    
	int* p1 = new int;
	int* p2 = new int;

	Div();

	delete p1;
	delete p2;
}

由于p1、p2、都需要释放,因此一旦在p2、p3出现了异常我们要手动释放前面的资源,这样的方式特别麻烦,这里还仅仅只是两个资源,一旦涉及更多new的资源会更麻烦,为了解决这个问题,c++引入了智能指针解决这个问题。

智能指针简单实现

一般而言智能指针要有三个问题:
1、利用对象的生命周期来控制程序资源,RAII的思想。
2、像指针一样使用。
3、考虑拷贝的问题。

unique_ptr

上面的例子中,如果p2、div出现了问题,前面的资源就无法释放,虽然可以通过重新抛出的方式来解决,但让代码可读性变得很差,同时也非常麻烦。使用智能指针可以解决这个问题,下面是一个智能指针的实例:

template<class T>
class SmartPtr
{
    
    
public:
	//构造保存指针
	SmartPtr(T* ptr)
		: _ptr(ptr)
	{
    
    

	}
	//析构释放资源
	~SmartPtr()
	{
    
    
		if(_ptr)
			delete _ptr;

		cout << "delete _ptr success !" << endl;
	}

	//模拟指针的两个行为
	T& operator*()
	{
    
    
		return *_ptr;
	}

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

这样完美地达到了控制资源释放的要求,但与此同时也引来了一个新的问题,即原生指针是可以拷贝的,但智能指针显然不可以拷贝,因为这里拷贝我们要求浅拷贝,那样对象在释放时同一份资源就会析构两次,这是非常可怕的。

为了解决这个问题,c++98库在实现auto_ptr的时候,使用了一种叫管理权转移的方式,如下代码,但其他这样效果非常不好。

//拷贝的悬空
SmartPtr(SmartPtr& sp)
	: _ptr(sp._ptr)
{
    
    
	sp._ptr = nullptr;
}

在后来的c++准标准库中,boost设计了一种新的智能指针,在c++11中相当于unique_ptr,该指针直接明令禁止不允许拷贝。

禁止别人拷贝的方式有很多,这里介绍两种:
在c++98中,一般只声明不实现,为了防止有人在类外动手脚,需要再用private封死这两个函数;相比之下c++11就更加简单,只需要写上delete关键字即可。

//c++11拷贝赋值禁止
	unique_ptr(const unique_ptr<T>& up) = delete;
	unique_ptr<T>& operator=(const unique_ptr<T>& up) = delete;
//c++98禁止:
private:
	unique_ptr(const unique_ptr<T>& up);
	unique_ptr<T>& operator=(const unique_ptr<T>& up);

shared_ptr

可以看出上面解决拷贝问题的方式本质就是规避了这个问题,shared_ptr不同,他允许我们进行拷贝构造。

要解决拷贝问题,实际上就是要控制好对象何时释放资源、释放几次的问题,我们发现引用计数很适合解决这个问题。

template<class T>
class shared_ptr
{
    
    
public:
	//构造保存指针
	shared_ptr(T* ptr)
		: _ptr(ptr)
		, _count(new int(1))
		, _pmtx(new mutex)
	{
    
    

	}
	//拷贝
	shared_ptr(const shared_ptr<T>& sp)
		: _ptr(sp._ptr)
	{
    
    
		if (_ptr != sp._ptr)
		{
    
    
			sp._count++;
		}
	}


	//析构释放资源
	~shared_ptr()
	{
    
    
		if ((--(*_count) == 0))
		{
    
    
			delete _ptr;
			delete _count;
			cout << "delete _ptr;" << " delete _count; " << endl;
		}
		cout << "delete _ptr success !" << endl;
	}

	//模拟指针的两个行为
	T& operator*()
	{
    
    
		return *_ptr;
	}

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


private:
	T* _ptr = nullptr;
	int* _count;
	mutex* _pmtx;
};


上述代码简单模拟了shared_ptr,但我们发现一旦使用了引用计数,不可避免地会出现线程安全问题,为了解决线程安全的问题,我们要对这些地方加锁。

template<class T>
class shared_ptr
{
    
    
public:
	//构造保存指针
	shared_ptr(T* ptr=nullptr)
		: _ptr(ptr)
		, _count(new int(1))
		, _pmtx(new mutex)
	{
    
    

	}

	//	+
	void AddCount()
	{
    
    
		_pmtx->lock();
		++(*_count);
		_pmtx->unlock();
	}

	//	-
	void Release()
	{
    
    
		_pmtx->lock();
		bool deleteFlag = false;

		if (--(*_count) == 0)
		{
    
    
			delete _ptr;
			delete _count;
			cout << "delete " << _ptr << endl;
			deleteFlag = true;
		}

		_pmtx->unlock();
		
		if (deleteFlag)
			delete _pmtx;
	}


	//拷贝构造
	shared_ptr(const shared_ptr<T>& sp)
		: _ptr(sp._ptr)
		, _count(sp._count)
		, _pmtx(sp._pmtx)
	{
    
    
		AddCount();
	}


	//赋值
	shared_ptr<T>& operator=(const shared_ptr<T>& sp)
	{
    
    
		if (_ptr != sp._ptr)
		{
    
    
			//这里是赋值之前的-
			Release();

			_ptr = sp._ptr;
			_count = sp._count;
			_pmtx = sp._pmtx;
			//注意这里加就是赋值之后的加了
			AddCount();
		}

		return *this;
	}


	//析构释放资源
	~shared_ptr()
	{
    
    
		Release();
	}

	//模拟指针的两个行为
	T& operator*()
	{
    
    
		return *_ptr;
	}

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

	T* get() const
	{
    
    
		return _ptr;
	}

	int use_count()
	{
    
    
		return *_count;
	}

private:
	T* _ptr;
	int* _count;
	mutex* _pmtx;
};

注意,shared_ptr本身是线程安全的,但管理的对象并不是线程安全的,需要加锁保护,在一些极端的场景下还会出现循环引用的问题。

循环引用和weak_ptr的引入

循环引用

struct ListNode
{
    
    
	int _val;
	nhy::shared_ptr<ListNode> _prev;
	nhy::shared_ptr<ListNode> _next;

	~ListNode()
	{
    
    
		cout << "~ListNode" << endl;
	}
};

如果链表的两个指针也使用shared_ptr,就会出现如图所示循环引用的问题,不仅仅p指向这个对象,还有一个next或prev也指向这个对象,这样双方会僵持不下,谁都无法释放。
在这里插入图片描述

weak_ptr

要解决这个问题,只需要将不必要的计数功能取消即可,其实weakptr本质就是sharedptr取消了计数功能。

template<class T>
class weak_ptr
{
    
    
public:
	weak_ptr()
		:_ptr(nullptr)
	{
    
    }

	weak_ptr(const shared_ptr<T>& sp)
		:_ptr(sp.get())
	{
    
    }

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

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

	T* get()
	{
    
    
		return _ptr;
	}

private:
	T* _ptr;
};

定制删除器

可以传入一个对象来管理释放资源。

template<class D>
shared_ptr(T* ptr, D del)
	: _ptr(ptr)
	, _count(new int(1))
	, _pmtx(new mutex)
	, _del(del)
{
    
    

}

function<void(T*)> _del = [](T* ptr) {
    
    
	cout << "default delete" << endl;
	delete ptr;
};

//  定制删除器 -- 可调用对象
template<class T>
struct DeleteArray
{
    
    
	void operator()(T* ptr)
	{
    
    
		cout << "void operator()(T* ptr)" << endl;
		delete[] ptr;
	}
};


class Date
{
    
    
private:
	int _year;
	int _month;
	int _day;
};

void test_delete()
{
    
    
	 nhy::shared_ptr<int> spa1(new int[10],DeleteArray<int>());
	 nhy::shared_ptr<Date> spa2(new Date[10]);
	 nhy::shared_ptr<Date> spa3(new Date[10],DeleteArray<Date>());
}

猜你喜欢

转载自blog.csdn.net/m0_73209194/article/details/131543659