C++(19)——智能指针shared_ptr

本文详细介绍了智能指针shared_ptr的工作原理和实现,包括其引用计数管理和对象资源的自动释放。通过模拟实现,展示了shared_ptr如何通过引用计数来管理对象生命周期,并讨论了在多线程环境下的安全问题。此外,文章还提到了解决循环引用问题的weak_ptr,以及在多线程环境中使用atomic保证线程安全的重要性。最后,强调了make_shared的优势,如减少堆区构建次数和提高效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

shared_ptr的概念

shared_ptr实现共享式拥有(shared ownership)概念。多个智能指针可以指向相同对象,该对象和其相关资源会在“最后一个引用(reference)被销毁”时候释放。

基本原理

智能指针是(几乎总是)模板类,shared_ptr 同样是模板类,所以在创建 shared_ptr 时需要指定其指向的类型。shared_ptr 负责在不使用实例时释放由它管理的对象,同时它可以自由的共享它指向的对象。
shared_ptr 使用经典的 “引用计数” 的方法来管理对象资源。

模拟实现理解原理

在系统提供的共享性智能指针中,存在一个观察器use_count用来返回shared_ptr所管理对象的引用计数,使用样例如下:
在这里插入图片描述

那么,其具体实现是怎样的呢?我们来模仿源码实现一下:

template<class _Ty>
class RefCnt
{
public:
	_Ty* mptr;
	//int ref;
	std::atomic_int ref;

public:
	RefCnt(_Ty* p = nullptr) :mptr(p), ref(mptr != nullptr) { //ref = mptr == nullptr?0:1;}
	~RefCnt() {}
};
template<class _Ty, class _Dx = MyDeletor<_Ty> >
class my_shared_ptr // thread;
{
public:
	my_shared_ptr(_Ty* p = nullptr) :ptr(nullptr)
	{
		if (p != nullptr)
		{
			ptr = new RefCnt(p);
		}
	}
	my_shared_ptr(const my_shared_ptr& _Y) :ptr(_Y.ptr)
	{
		if (ptr != nullptr)
		{
			ptr->ref += 1;
		}
	}// my_shared_ptr<Object> op2(op1);
	my_shared_ptr(my_shared_ptr&& _Y) :ptr(_Y.ptr)
	{
		_Y.ptr = nullptr;
	}// my_shared_ptr<Object> op2(std::move(op1));
	operator bool() const { return ptr != nullptr; }

	my_shared_ptr& operator=(const my_shared_ptr& _Y) // 
	{
		if (this == &_Y || this->ptr == _Y.ptr) return *this;
		if (ptr != NULL && --ptr->ref == 0)
		{
			mDeletor(ptr);
		}
		ptr = _Y.ptr;
		if (ptr != nullptr)
		{
			ptr->ref += 1;
		}
		return *this;
	}
	my_shared_ptr& operator=(my_shared_ptr&& _Y) // move operator =
	{
		if (this == &_Y) return *this;
		if (this->ptr == _Y.ptr && this->ptr != nullptr && _Y.ptr != nullptr)
		{
			this->ptr->ref -= 1;
			_Y.ptr = nullptr;
			return * this;
		}
		if (this->ptr != nullptr && --ptr->ref == 0)
		{
			mDeletor(ptr);
		}
		ptr = _Y.ptr;
		_Y.ptr = nullptr;
		return *this;
	}
	void reset(_Ty* p = nullptr)
	{
		if (this->ptr != nullptr && --this->ptr->ref == 0)
		{
			mDeletor(ptr);
		}
		ptr = new RefCnt<_Ty>(p);
	}
	~my_shared_ptr()
	{
		if (this->ptr != nullptr && --this->ptr->ref == 0)
		{
			mDeletor(ptr->mptr);
			delete ptr;
		}
		ptr = nullptr;
	}
	_Ty* get() const { return ptr->mptr; }
	_Ty& operator*() const
	{
		return *get();
	}
	_Ty* operator->() const
	{
		return get();
	}

	size_t use_count() const
	{
		if (this->ptr == nullptr) return 0;
		return this->ptr->ref;
	}
	void swap(my_shared_ptr& r)
	{
		std::swap(this->ptr, r.ptr);
	}

private:
	RefCnt<_Ty>* ptr;
	_Dx mDeletor;
};

template<class _Ty, class _Dx >
class my_shared_ptr<_Ty[],_Dx>
{
public:
	my_shared_ptr(_Ty* p = nullptr) :ptr(nullptr)
	{
		if (p != nullptr)
		{
			ptr = new RefCnt(p);
		}
	}
	my_shared_ptr(const my_shared_ptr& _Y) :ptr(_Y.ptr)
	{
		if (ptr != nullptr)
		{
			ptr->ref += 1;
		}
	}// my_shared_ptr<Object> op2(op1);
	my_shared_ptr(my_shared_ptr&& _Y) :ptr(_Y.ptr)
	{
		_Y.ptr = nullptr;
	}// my_shared_ptr<Object> op2(std::move(op1));
	operator bool() const { return ptr != nullptr; }

	my_shared_ptr& operator=(const my_shared_ptr& _Y) // 
	{
		if (this == &_Y || this->ptr == _Y.ptr) return *this;
		if (ptr != NULL && --ptr->ref == 0)
		{
			mDeletor(ptr->mptr);
			delete ptr;
		}
		ptr = _Y.ptr;
		if (ptr != nullptr)
		{
			ptr->ref += 1;
		}
		return *this;
	}
	my_shared_ptr& operator=(my_shared_ptr&& _Y) // move operator =
	{
		if (this == &_Y) return *this;
		if (this->ptr == _Y.ptr && this->ptr != nullptr && _Y.ptr != nullptr)
		{
			this->ptr->ref -= 1;
			_Y.ptr = nullptr;
			return * this;
		}
		if (this->ptr != nullptr && --ptr->ref == 0)
		{
			mDeletor(ptr->mptr);
			delete ptr;
		}
		ptr = _Y.ptr;
		_Y.ptr = nullptr;
		return *this;
	}
	void reset(_Ty* p = nullptr)
	{
		if (this->ptr != nullptr && --this->ptr->ref == 0)
		{
			mDeletor(ptr->mptr);
			delete ptr;
		}
		ptr = new RefCnt<_Ty>(p);
	}
	~my_shared_ptr()
	{
		if (this->ptr != nullptr && --this->ptr->ref == 0)
		{
			mDeletor(ptr->mptr);
			delete ptr;
		}
		ptr = nullptr;
	}
	_Ty* get() const { return ptr->mptr; }
	_Ty& operator*() const
	{
		return *get();
	}
	_Ty* operator->() const
	{
		return get();
	}

	size_t use_count() const
	{
		if (this->ptr == nullptr) return 0;
		return this->ptr->ref;
	}
	void swap(my_shared_ptr& r)
	{
		std::swap(this->ptr, r.ptr);
	}

	_Ty& operator[](const int idx) const
	{
		return ptr->mptr[idx];
	}

private:
	RefCnt<_Ty>* ptr;
	_Dx mDeletor;
};

根据代码:我们分析到对象的内存结构图如下:这有助于我们更好地理解:
在这里插入图片描述

相互引用问题

在这里插入图片描述
退出之前,它们的 use_count() 都为2,退出了 fun() 后,由于 c和 p 对象互相引用,它们的引用计数都是 1,不能自动释放(可以看到没有调用析构函数),并且此时这两个对象再无法访问到。这就引起了c++中那臭名昭著的“内存泄漏”。

我们解决这个问题就可以使用weak_ptr:
使用weak_ptr 来打破循环引用,它与一个 shared_ptr 绑定,但却不参与引用计数的计算,不论是否有 weak_ptr 指向,一旦最后一个指向对象的 shared_ptr 被销毁,对象就会被释放。weak_ptr 像是 shared_ptr 的一个助手。同时,在需要时,它还能摇身一变,生成一个与它绑定的 shared_ptr 共享引用计数的shared_ptr。

多线程下的shared_ptr存在的问题

因为在多线程下,多个线程很有可能对同一个shared_ptr的引用计数进行操作,然而在我们设计的shared_ptr中的引用计数为int类型,对于int的一个操作其实并不是原子操作,那么多线程环境下就不能保证安全的运行。
于是我们会对引用计数进行一个重新的设计:
引入#include<atomic>,修改引用计数的类型如下:
在这里插入图片描述
从而解决这一问题。

总结

(1) 智能指针主要的用途就是方便资源的管理,自动释放没有指针引用的资源。

(2) 使用引用计数来标识是否有多余指针指向该资源。(注意,shart_ptr本身指针会占1个引用)

(3) 在赋值操作中, 原来资源的引用计数会减一,新指向的资源引用计数会加一。

(4) 引用计数加一/减一操作是原子操作,所以线程安全的。

(5) make_shared要优于使用new,make_shared可以一次将需要内存分配好。(第一张图片中有解释)(下文有拓展

(6) std::shared_ptr的大小是原始指针的两倍,因为它的内部有一个原始指针指向资源,同时有个指针指向引用计数。

(7) 引用计数是分配在动态分配的,std::shared_ptr支持拷贝,新的指针获可以获取前引用计数个数。

拓展:使用make_shared的三个理由
  1. 减少在堆区构建对象的次数;
  2. 为了提高命中率,使被管理对象和引用计数结构在同一个空间,访问速度加快(因为因为计数器内存和原生内存排排坐)
  3. 在一些调用次序不确定的情况下,使得被管理对象仍然处于可控阶段;

对于第三点的解释:
在这里插入图片描述

上面的代码在执行过程中,有三件事情需要完成,构建Object,构建shared_ptr以及调用couldThrowException(),C++17引入了更加严格的函数参数的构建顺序,但在那之前,上面三件事的构建顺序如下:

  1. new Object;
  2. 调用couldThrowException
  3. 构建sahred_ptr并管理步骤1的内存

所以存在的问题就是:一旦步骤二抛出异常,步骤三就不会发生,智能指针很无辜,这不是它造成的,然而一旦使用std::make_shared的方式就会使得步骤1和3紧挨在一起,也就不会发生刚才的事情。

拓展:make_shared的坏处

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值