C++:共享型智能指针 | 模拟实现my_shared_ptr

本文详细介绍了如何自定义C++智能指针shared_ptr,包括其结构、创建方法、引用计数管理以及关键成员函数的实现,如拷贝构造、移动构造、赋值操作等。通过仿写,读者能更好地掌握共享所有权的概念和实际编程应用。
摘要由CSDN通过智能技术生成

前言

在前面的文章中,完成了auto_ptr、unique_ptr的介绍和仿写,本文将介绍共享型智能指针——shared_ptr。

本文所供智能指针使用的对象

class Object
{
private:
	int num;
public:
	Object(int x = 0) : num(x) {
		cout << "Create Object: " << this << endl;
	}
	~Object() {
		cout << "Destroy Object: " << this << endl;
	}
	int& value() { return num; }
	const int& value() const {
		return num;
	}
};

一、共享型智能指针

shared_ptr是一个引用计数的智能指针,用于共享对象的所有权,即可以让多个指针指向同一个对象,这一点和原始指针相同。

1.shared_ptr的结构

共享智能指针可以理解为:
shared_ptr指向一个引用计数的结构,这个结构里有一个引用计数(有shared_ptr的引用计数,还有一个weak_ptr的引用计数,这里只说shared_ptr),还有一个指向目标对象的指针

示例:

int main(void)
{
	std::shared_ptr<Object> sp1(new Object(10));

	return 0;
}

在这里插入图片描述

可简单理解为:
在这里插入图片描述

当两个指针同时指向同一个对象时,那么指向的引用计数就会加一。

int main(void)
{
	std::shared_ptr<Object> sp1(new Object(10));

	std::shared_ptr<Object> sp2(sp1);

	return 0;
}

运行过程中可以看出,当拿一个智能指针构造另一个智能指针,两个指针除了本身的地址不同,其余都是共同指向一套东西。且引用计数变为2,表示该资源由两个指针共享。
在这里插入图片描述

2. 创建shared_ptr实例

  1. 最安全和高效的方法是调用make_shared库函数<>/font,该函数会在堆中分配一个对象并初始化,最后返回指向此对象的share_ptr实例。
  2. 如果你不想使用make_ptr,也可以先明确new出一个对象,然后把其原始指针传递给share_ptr的构造函数。
int main(void)
{
	std::shared_ptr<Object> sp1 = std::make_shared<Object>(10);

	Object* num = new Object(10);
	std::shared_ptr<Object> sp2(num);

	return 0;
}

3. 检查引用计数

shared_ptr提供了两个函数来检查其共享的引用计数值,分别是unique()和use_count()。
use_count()函数,该函数返回当前指针的引用计数值。值得注意的是use_count()函数可能效率很低,应该只把它用于测试或调试。
unique()函数用来测试该shared_ptr是否是原始指针唯一拥有者,也就是use_count()的返回值为1时返回true,否则返回false。

int main(void)
{
	std::shared_ptr<Object> sp1 = std::make_shared<Object>(10);

	Object* num = new Object(10);
	std::shared_ptr<Object> sp2(num);

	cout << sp1.use_count() << endl;
	cout << sp2.unique() << endl;

	return 0;
}

在这里插入图片描述

二、仿写shared_ptr

(一)辅助类:删除器

和my_unque_ptr的删除器相同,可以直接使用

1.删除单个对象

template<class _Ty>
class MyDeletor
{
public:
	MyDeletor() = default;
	void operator()(_Ty* ptr) const
	{
		if (ptr != nullptr)
		{
			delete ptr;
		}
	}
};

2.删除一组对象

template<class _Ty>
class MyDeletor<_Ty[]>
{
public:
	MyDeletor() = default;
	void operator()(_Ty* ptr) const
	{
		if (ptr != nullptr)
		{
			delete[]ptr;
		}
	}
};

(二)引用计数类

template<typename _Ty>
class RefCnt
{
private:
	_Ty* mptr; // 指向对象的指针
	int ref;   // 引用计数 
public:
	RefCnt(_Ty* p = nullptr) : mptr(p), ref(mptr != nullptr)
	{
	}
	~RefCnt() {}
};

(三) 指向单独对象的my_shared_ptr

这个类的成员变量由一个指向引用计数的指针和一个删除器组成。
在构造函数中,判断是否初始化,如果初始化为nullptr,那就什么都不做,如果不为nullptr,就在堆区申请一个引用计数类型。


template<class _Ty, class _Dx = MyDeletor<_Ty> >
class my_shared_ptr
{
public:
	my_shared_ptr(_Ty* p = nullptr) :ptr(nullptr)
	{
		if (p != nullptr)
		{
			ptr = new RefCnt(p);
		}
	}
private:
	RefCnt<_Ty>* ptr;
	_Dx mDeletor;
};

1.拷贝构造函数

由于是共享型的指针,所以拷贝构造时直接将智能指针的指针成员赋值给当前this的指针,在判断是否为nullptr,若不为空,则引用计数加一。

my_shared_ptr(const my_shared_ptr& _Y) :ptr(_Y.ptr)
{
	if (ptr != nullptr)
	{
		ptr->ref += 1;
	}
}

2. 移动构造函数

移动构造函数是资源转移,所以无需其他操作,让当前this的指针成员也指向参数指针所指的引用计数,让参数的指针置为nullptr。

my_shared_ptr(my_shared_ptr&& _Y) :ptr(_Y.ptr)
{
	_Y.ptr = nullptr;
}

3.重载bool()运算符

operator bool() const { return ptr != nullptr; }

4. 赋值构造函数

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;
	}

为什么赋值构造要这样写?因为要满足多种情况。

  1. 当自己给自己赋值时,直接返回。
  2. 在赋值前要先将自身的资源移交出去,即就是将自身指向的引用计数减一,如果减一后引用计数为0,那么就调用删除器将资源删除,表示没有指针再指向资源对象。之后再将参数的指针赋值给当前指针;如果当前指针不为nullptr,引用计数加一。

在这里插入图片描述

  1. 如果要拿一个空指针赋给有资源的指针:自身引用计数减一,若减完为0,则调用删除器。拿空指针赋值,然后返回。

在这里插入图片描述

满足多种不同情况,才算完整的写完赋值构造函数。

5. 移动赋值函数

// move operator =
my_shared_ptr& operator=(my_shared_ptr&& _Y) 
{
	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;
}

同样是要考虑多种情况:

  1. 自赋值情况
  2. 两个指针指向相同的情况,如果指向相同,那么将引用计数减一,再将参数的指针置为空,即可返回。
  3. 如果当前指针不为空且引用计数减一后为0,那么调用删除器将资源对象删除,再用参数对其赋值。
  4. 若当前指针指向空,则直接将当前指针指向参数的指针所指的对象,并将参数的指针置为空。(不管参数的指针是否指向空,都可执行)。

6.reset()函数

将指针重新指向其他资源

  1. 若当前指针不为空,且引用计数减一后等于0,那么调用删除器;如果引用计数减一不为0,说明还有其他指针拥有该对象。
  2. 指针指向 重新向堆区申请引用计数的结构,该结构的指针指向参数类型的对象。
void reset(_Ty* p = nullptr)
{
	if (this->ptr != nullptr && --this->ptr->ref == 0)
	{
		mDeletor(ptr);
	}
	ptr = new RefCnt<_Ty>(p);
}

7.析构函数

析构的是当前指针,那么

  1. 先判断是否为空,再引用计数减一,如果结果为0,则调用删除器先删除引用计数所指的对象,再删除当前指针
  2. 将当前指针置为空。
~my_shared_ptr()
{
	if (this->ptr != nullptr && --this->ptr->ref == 0)
	{
		mDeletor(ptr->mptr);
		delete ptr;
	}
	ptr = nullptr;
}

8. 观察器:重载解引用和指向符

	_Ty* get() const { return ptr->mptr; }
	_Ty& operator*() const
	{
		return *get();
	}
	_Ty* operator->() const
	{
		return get();
	}

9. 获得当前引用计数个数

即查看当前对象有多少个智能指针共享

size_t use_count() const
{
	if (this->ptr == nullptr) return 0;
	return this->ptr->ref;
}

10.交换资源函数

void swap(my_shared_ptr& r)
{
	std::swap(this->ptr, r.ptr);
}

(四)指向一组对象的my_shared_ptr

这种指针的删除器是删除一组对象的,模板也需要特化一下。

template<class _Ty, class _Dx >
class my_shared_ptr<_Ty[], _Dx>
{
private:
	_Ty* ptr;
	_Dx mDeletor;
public:
	my_shared_ptr(_Ty* p = nullptr) :ptr(nullptr)
	{
		if (p != nullptr)
		{
			ptr = new RefCnt(p);
		}
	}
};

区别点

删除一组对象时大部分函数的调用删除器操作都需要改变:
需要先将指针所指向的引用计数指针传递给删除器,再删除当前指针。

示例:相关操作都需要这样改写。

if (ptr != nullptr && --ptr->ref == 0)
{
	mDeletor(ptr->mptr);
	delete ptr;
}

提供到被管理数组的有索引访问

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

END

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_索伦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值