智能指针(2)

shared_ptr(共享型智能指针)

基础知识

在java中有一个垃圾回收器,可以运用到所有资源,heap内存和系统资源都可以使用的系统,而C++11中的shared_ptr就是为了达到这样的目的,其和唯一性智能指针不同,共享型智能指针没有一个特定的指针拥有资源对象,而是这些指针指向同一资源对象,相互协作来管理该对象,在不需要时进行析构。

特点

  • 共享所有权模式
  • 使用了引用技术控制管理资源对象的生命周期
  • 提供拷贝构造函数和赋值重载函数;提供移动构造和移动赋值。
  • 添加了删除器类型
  • 在容器中保存shared_ptr对象是安全的
  • 重载了operator->和operator*运算符,因此可以像普通指针一样使用

引用计数器

引用计数器的值为指向资源的智能指针数,当智能指针对象死亡时,判断引用计数器的值,如果不是1,智能指针本身析构,不释放资源,如果是1,析构智能指针,释放资源。

共享型智能指针结构理解

此处用自己设计的整型举例,不做编写

int main() {
	std::shared_ptr<Int> pa(new Int(10));
	cout << pa.use_count() << endl;
	std::shared_ptr<Int>pb(pa);
	cout << pa.use_count() << endl;
	std::shared_ptr<Int> pc;
	pc = pa;
	cout << pa.use_count() << endl;
	return 0;
}

其输出结果是这样的:
在这里插入图片描述

为什么会输出这样的结果呢?我们可以画图来理解智能指针的结构。
在这里插入图片描述
这里我只画了pc对象的结构,其结构都是一样的,创建pa对象的时候,其有三个成员对象,一个是删除器,另外两个是指针,指向堆区的空间,其中一个指针指向我们的Int对象,另一个指向我们在堆区创建的计数器对象,计数器中存在一个指针和两个计数器,其指针指向我们的Int对象,两个计数器分别是_Uses和_Weaks,这里个计数器分别是共享型智能指针和弱引用智能指针。这里暂时不给大家说说这两者的区别,后面会在循环引用中讲解,大家只需要知道当创建智能指针时,其资源数+1。所以呢我们输出结果才是123。
这里如果大家还不是很懂,我们再对其进行简单的仿写。

shared_ptr仿写

删除器类

删除器类型就是为了通过仿函数来析构对象,增强代码的可用性。

template<class _Ty>
struct my_deleter
{
	void operator()(_Ty* ptr) const
	{
		delete ptr;
	}
};
template<class _Ty>
struct my_deleter<_Ty[]>
{
	void operator()(_Ty* ptr) const
	{
		delete[]ptr;
	}
};

计数器类

class My_RefCount
{
public:
	using element_type = _Ty;
	using pointer = _Ty*;
private:
	_Ty* _Ptr;
	std::atomic<int> _Uses; // shared;
	std::atomic<int> _Weaks; // weak_ptr;
public:
	My_RefCount(_Ty* ptr = nullptr)
		:_Ptr(ptr), _Uses(0), _Weaks(0)
	{
		if (_Ptr != nullptr)
		{
			_Uses = 1;
			_Weaks = 1;
		}
	}
	~My_RefCount() = default;
	void Incref()
	{
		_Uses += 1;
	}
	void Incwref()
	{
		_Weaks += 1;
	}
	int Decref()
	{
		if (--_Uses == 0)
		{
			Decwref();
		}
		return _Uses;
	}
	int Decwref()
	{
		_Weaks -= 1;
		return _Weaks;
	}
	int _use_count() const
	{
		return _Uses.load();
	}
};

在计数器类型中成员方法就是将两个计数器进行+1,-1操作,返回当前指向资源的计数器。也没有太多要说的,主要是其结构,上面说了计数器类型,其有三个成员对象,分别是ptr指针,指向资源对象,而另外两个便是整型计数器,但是其写法是这样的 std::atomic<int> _Uses; // shared; std::atomic<int> _Weaks; // weak_ptr; 这样的写法是让其这两个计数器具有原子性。而原子性是什么呢?原子性:我们发现在多线程编程中,我们举一个例,有三个线程,和一个全局变量a=1,每个线程中对该变量进行++操作100次,我们可以发现结果有可能不是300,这是因为有几个线程同时对变量+1,导致两次+1操作本来+2,变成了+1一次,所以呢会小于300,有了原子性,便不会出现这样的操作,在++的时刻不会出现其他线程也+1操作。和锁的作用类似,但是大家对互斥锁的底层有所了解的话会知道其是从就绪态,运行态,阻塞态不停的切换,这样的话比该方法效率低得多。

shared_ptr类

template<class _Ty, class Deleter = my_deleter<_Ty> >
class my_shared_ptr
{
private:
	_Ty* mPtr;
	My_RefCount<_Ty>* mRep;
	Deleter mDeleter;
public:
	my_shared_ptr(_Ty* ptr = nullptr) :mPtr(ptr), mRep(nullptr)
	{
		if (mPtr != nullptr)
		{
			mRep = new My_RefCount(mPtr);
		}
	}
	my_shared_ptr(const my_shared_ptr& src) :mPtr(src.mPtr), mRep(src.mRep)
	{
		if (mRep != nullptr)
		{
			mRep->Incref();
		}
	}
	~my_shared_ptr()
	{
		if (mRep != nullptr && mRep->Decref() == 0)
		{
			mDeleter(mPtr);
			delete mRep;
		}
		mPtr = nullptr;
		mRep = nullptr;
	}
	my_shared_ptr(my_shared_ptr&& right) :
		mPtr(right.mPtr), mRep(right.mRep) {
		right.mPtr = nullptr;
		right.mRep = nnullptr;
	}

	/*my_shared_ptr& operator=(const my_shared_ptr& src) {
		if (this == &src) return*this;
		if (mRep != nullptr && mRep->Decref() == 0) {
			mDeleter(mPtr);
			delete mRep;
		}
		mPtr = src.mPtr;
		mRep = src.mRep;
		if (mRep != nullptr) {
			mRep->Incref();
		}
		return *this;
	}*/
	my_shared_ptr& operator=(const my_shared_ptr& src) {
		my_shared_ptr(src).swap(*this);
	}
	/*my_shared_ptr& operator=(my_shared_ptr&& right) {
		if (this == &right) return *this;
		if (mRep != nullptr && mRep->Decref() == 0) {
			mDeleter(mPtr);
			delete mRep;
		}
		mPtr = right.mPtr;
		mRep = right.mRep;
		right.mPtr = nullptr;
		right.mRep = nullptr;
	}*/
	my_shared_ptr& operator=(my_shared_ptr&& right) {
		if (this == &right) return *this;
		my_shared_ptr(std::move(right)).swap(*this);
		return*this;
	}
	int use_count() const {
		return mRep != nullptr ? mRep->_use_count() : 0;
	}

	_Ty* get()const {
		return mPtr;
	}

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

	operator bool()const {
		return mPtr != nullptr;
	}
	void swap(my_shared_ptr& other) {
		std::swap(this->mPtr, other.mPtr);
		std::swap(this->mRep, other.mRep);
	}

	void reset() {
		if (mRep->Decref() == 0) {
			mDeleter(mPtr);
			delete mRep;
		}
		mPtr = nullptr;
		mRep = nullptr;
	}
	void reset(_Ty* p) {
		if (nullptr == p) {
			reset();
		}
		if (mRep->Decref() == 0) {
			mDeleter(mPtr);
			delete mRep;
		}
		mPtr = p;
		mRep = new My_RefCount(ptr);
	}

};

使用以及仿写代码的理解

构造函数:共享型指针的时候呢,使用指向资源的指针初始化对象中的指针,然后用该指针构建计数器对象。
拷贝构造:将原本智能指针类型的两个指针进行赋值,然后对计数器中的智能指针计数器+1操作。
析构函数:如果指向对象的指针不为nullptr并且其计数器自减之后不为0,将其两个指针置为nullptr,如果自减之后为0则进行释放资源对象和计数器对象。
移动构造:将其指针进行赋值然后将形参的两个指针置为nullptr
重载赋值运算符:这里呢有两种方法,第一种是判断原本对象自减之后是否为0,然后进行释放资源,然后将待赋值对象进行赋值,如果对象不为nullptr,则计数器+1,另一种用到了置换函数,也是共享型智能指针中源码的写法,很是奇妙。使用形参创建一个临时对象,然后将其和this指向的对象进行资源置换。创建的临时对象,现在存放的是 this指向原本的资源,然后因为是临时对象,所以最后会析构一次,调用析构函数,判断其计数器是否为0来决定其是否释放对象,而创建src对象的时候便对其计数器进行了+1操作,然后资源置换给了this指针。
移动语义的赋值运算符重载:这里和重载赋值运算符大同小异,大家可以试着自行理解,同样也有和上面一样创建临时对象的写法,可以试着理解。
reset(_Ty p)*:该函数是资源覆盖,如果p为nullptr,直接调用无参的reset函数来释放该智能指针对象。如果不为nullptr,则先判断计数器自减之后为否为0来决定是否释放资源,然后使用参数指针来构造新的计数器对象。

循环引用

class Child;

class Parent
{
public:
 std::shared_ptr<Child> child;
 Parent() { cout << "Parent" << endl; }
 ~Parent() { cout << "~Parent" << endl; }

 void Hi() { cout << "hello Parent" << endl; }
};
class Child
{
public:
 std::shared_ptr<Parent> parent;
 Child() { cout << "Child" << endl; }
 ~Child() { cout << "~Child" << endl; }
};

int main()
{
 std::shared_ptr<Parent> pa(new Parent());
 std::shared_ptr<Child> pc(new Child());
 pa->child = pc;
 pc->parent = pa;

 return 0;
}

运行上面代码我们会发现两个对象都没有析构,这是为什么呢?我们画图来理解:
在这里插入图片描述
创建pa对象时,其两个指针分别指向临时的智能指针对象child,计数器对象,而计数器child对象种也有两个指针都为nullptr,然后船舰pc对象时也是样的,接着将pc赋值给pa的智能指针child对象,这样pc对象的计数器智能指针计数器+1,变成了2,然后呢pa也赋值给了pc的智能指针parent对象,导致两者析构的时候都对计数器进行了自减操作,但是自减之后都不为0所以不能析构。而怎么解决这个问题呢?

_Weaks

这就说到了我们的_Weaks弱指针计数器,这时候我们需要在pa和pc类种用弱引用智能指针(weak_ptr),其+1是对_Weaks计数器进行+1,这样就会可以析构对象了,但是有人会说那么弱智能指针的计数器不为0,那怎么办呢?
实际上这两个计数器控制的是两块堆内存,如果智能指针计数器为0,释放指向资源对象的堆内存,两个计数器都为0时释放计数器的堆内存。

int main()
{
	std::shared_ptr<Parent> pa(new Parent());
	std::shared_ptr<Child> pc(new Child());
	pa->child = pc;
	pc->parent = pa;
	pc->parent.lock()->Hi();

	return 0;
}

weak_ptr没有重载operator->和operator*操作符,因此不可以直接通过weak_ptr对象,典型的用法是调用lock函数来获得shared_ptr实例,进而访问对象。
在用了弱引用智能指针怎么调用成员函数呢?
我们发现上面代码中调用了一个lock()函数,因为弱引用智能指针没有重载->和解引用,为此通过调用lock函数获得shared_ptr,判断其共享型引用计数器是否为0来判断是否可以访问原始对象。

初始化智能指针的方法

初始化有下面两种方法:

int main()
{
	std::shared_ptr<int> ip(new int(10));//A
	map<string, std::shared_ptr<int>> it;
	it["xxin"] = std::make_shared<int>(new int(200));//B
	it["lxin"] = std::make_shared<int>(new int(100));
	return 0;
}

这两种方法呢也各有不同,B的速度比A块,但是呢也有坏处,为什么呢?
我们知道智能指针在堆区申请了两块空间,是两次申请的,而每一次申请都很费时间,而B方法申请是一次性申请够两块内存,比A方法申请两次效率高。我们知道在智能指针计数器为0时释放资源对象,弱引用智能指针计数器也为0时释放计数器,很明显有时这两块资源不能一起释放,当资源对象内存较小时可以等待弱引用智能指针计数器为0一起释放,但当内存很大时等待其结束释放便时很效率很低的。(一次申请的堆内存智能一次释放完,不能分多次释放)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
智能指针可以绑定到普通指针上,但是需要注意一些事项。默认情况下,智能指针使用delete释放它所关联的对象,因此一个用来初始化智能指针的普通指针必须指向动态内存。如果想将智能指针绑定到指向其他类资源的指针上,需要提供自己的操作来替代delete。不建议混合使用普通指针和智能指针,因为这可能导致资源释放的问题。\[1\] 智能指针是模板类,创建智能指针时需要提供额外的信息,即指针可以指向的类。默认初始化的智能指针保存着一个空指针。例如,shared_ptr允许多个指针指向同一对象,可以指向不同类的资源。\[2\] 在使用智能指针时,可以将指向用new申请的资源的普通指针传递给智能指针进行初始化构造。之后,可以像使用普通指针一样使用智能指针智能指针的一个优点是,在离开作用域后,它会自动释放所管理的资源,避免内存泄漏的风险。\[3\] #### 引用[.reference_title] - *1* *2* [C++ 智能指针](https://blog.csdn.net/Cdreamfly/article/details/123096008)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [C++智能指针](https://blog.csdn.net/weixin_54940900/article/details/114567979)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

*闲鱼

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

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

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

打赏作者

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

抵扣说明:

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

余额充值