C/C++|智能指针的shared_from_this和enable_shared_from_this

参考博客(或者叫摘抄的博客,本博客只做为个人学习使用):

施磊老师牛逼
深入掌握C++智能指针
C++智能指针的enable_shared_from_this和shared_from_this机制

文章目录

    • [再探 shared_ptr](#再探 shared_ptr)
    • 错误一
    • 修改错误一
    • 错误二
    • [修改错误二 enable_shared_from_this和shared_from_this](#修改错误二 enable_shared_from_this和shared_from_this)

再探 shared_ptr

在此之前,笔者已经写过一篇关于智能指针的博客【智能指针的实现及智能指针的浅拷贝问题:shared_ptr和weak_ptr详细解读】

为了引出我们的主题,先再一起讨论一下 shared_ptr。

shared_ptr 智能指针都我们知道,肯定是在栈上面的,那么,现在问题来了:"shared_ptr智能指针的引用计数在哪里存放? "

在shared_ptr的源码中是这样的:

cpp 复制代码
private:
	/*
	下面这两个是shared_ptr的成员变量,_Ptr是指向内存资源的指针,_Rep是
	指向new出来的计数器对象的指针,该计数器对象包含了资源的一个引用计数器count
	_Ptr_base的两个成员变量,这里只罗列了_Ptr_base的部分代码
	*/
	element_type * _Ptr{nullptr};
	_Ref_count_base * _Rep{nullptr};

**很明显,shared_ptr智能指针的资源引用计数器在内存的heap堆上。**也就是说,当我们定义一个 shared_ptr<int> ptr(new int)的智能指针对象时,该智能指针对象本身的内存是:

那么把智能指针管理的外部资源以及引用计数资源都画出来的话,如下图所示:

所以说,如果我们这样操作代码:

cpp 复制代码
shared_ptr<int> ptr1(new int);
shared_ptr<int> ptr2(ptr1);
cout<<ptr1.use_count()<<endl;
cout<<ptr2.use_count()<<endl;

这段代码没有任何问题,ptr1和ptr2管理了同一个资源,引用计数打印出来的都是2,出函数作用域依次析构,最终new int资源只释放一次,逻辑正确!这是因为shared_ptr ptr2(ptr1)调用了shared_ptr的拷贝构造函数(源码可以自己查看下),只是做了资源的引用计数的改变,没有额外分配其它资源,如下图所示:

错误一

如果我们这样操作代码:

cpp 复制代码
int *p = new int;
shared_ptr<int> ptr1(p);
shared_ptr<int> ptr2(p);
cout<<ptr1.use_count()<<endl;
cout<<ptr2.use_count()<<endl;

shared_ptr ptr1( p )和shared_ptr ptr2( p )都调用了shared_ptr的构造函数,在它的构造函数中,都重新开辟了引用计数的资源,导致ptr1和ptr2都记录了一次new int的引用计数,都是1,析构的时候它俩都去释放内存资源,导致释放逻辑错误,如下图所示:

上面两个代码段,分别是shared_ptr的构造函数和拷贝构造函数做的事情,导致虽然都是指向同一个new int资源,但是对于引用计数对象的管理方式,这两个函数是不一样的,构造函数是新分配引用计数对象,拷贝构造函数只做引用计数增减。

相信说到这里,大家知道最开始的两个代码清单上的代码为什么出错了吧,因为每次调用的都是shared_ptr的构造函数,虽然大家管理的资源都是一样的,_Ptr都是指向同一个堆内存,但是_Ref却指向了不同的引用计数对象,并且都记录引用计数是1,出作用域都去析构,导致问题发生

修改错误一

就是在产生同一资源的多个shared_ptr的时候,通过拷贝构造函数或者赋值operator=函数进行,不要重新构造,避免产生多个引用计数对象,代码修改如下:

cpp 复制代码
int main()
{
	A *p = new A(); // 裸指针指向堆上的对象

	shared_ptr<A> ptr1(p);// 用shared_ptr智能指针管理指针p指向的对象
	shared_ptr<A> ptr2(ptr1);// 用ptr1拷贝构造ptr2
	// 下面两次打印都是2,最终随着ptr1和ptr2析构,资源只释放一次,正确!
	cout << ptr1.use_count() << endl; 
	cout << ptr2.use_count() << endl;

	return 0;
}

错误二

cpp 复制代码
#include <iostream>
using namespace std;
// 智能指针测试类
class A
{
public:
	A():mptr(new int) 
	{
		cout << "A()" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
		delete mptr; 
		mptr = nullptr;
	}
	 
	// A类提供了一个成员方法,返回指向自身对象的shared_ptr智能指针。
	shared_ptr<A> getSharedPtr() 
	{ 
		/*注意:不能直接返回this,在多线程环境下,根本无法获知this指针指向
		的对象的生存状态,通过shared_ptr和weak_ptr可以解决多线程访问共享		
		对象的线程安全问题,参考施磊老师的另一篇介绍智能指针的博客*/
		return shared_ptr<A>(this); 
	}
private:
	int *mptr;
};
int main()
{
	shared_ptr<A> ptr1(new A());
	shared_ptr<A> ptr2 = ptr1->getSharedPtr();

	/* 按原先的想法,上面两个智能指针管理的是同一个A对象资源,但是这里打印都是1
	导致出main函数A对象析构两次,析构逻辑有问题*/
	cout << ptr1.use_count() << endl; 
	cout << ptr2.use_count() << endl;

	return 0;
}

代码同样有错误,A对象被析构了两次,而且看似两个shared_ptr指向了同一个A对象资源,但是资源计数并没有记录成2,还是1,不正确。

修改错误二 enable_shared_from_this和shared_from_this

案例二我们应该如何修改呢?注意我们有时候想在类里面提供一些方法,返回当前对象的一个shared_ptr强智能指针,做参数传递使用(多线程编程中经常会用到)。

首先肯定不能像上面代码清单2那样写return shared_ptr< A > ( this ) ,这会调用shared_ptr智能指针的构造函数,对this指针指向的对象,又建立了一份引用计数对象,加上main函数中的shared_ptr< A > ptr1(new A());已经对这个A对象建立的引用计数对象,又成了两个引用计数对象,对同一个资源都记录了引用计数,为1,最终两次析构对象释放内存,错误!

那如果一个类要提供一个函数接口,返回一个指向当前对象的shared_ptr智能指针怎么办?方法就是继承enable_shared_from_this类,然后通过调用从基类继承来的shared_from_this()方法返回指向同一个资源对象的智能指针shared_ptr

修改如下:

cpp 复制代码
#include <iostream>
using namespace std;
// 智能指针测试类,继承enable_shared_from_this类
class A : public enable_shared_from_this<A>
{
public:
	A() :mptr(new int)
	{
		cout << "A()" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
		delete mptr;
		mptr = nullptr;
	}

	// A类提供了一个成员方法,返回指向自身对象的shared_ptr智能指针
	shared_ptr<A> getSharedPtr()
	{
		/*通过调用基类的shared_from_this方法得到一个指向当前对象的
		智能指针*/
		return shared_from_this();
	}
private:
	int *mptr;
};
int main()
{
	shared_ptr<A> ptr1(new A());
	shared_ptr<A> ptr2 = ptr1->getSharedPtr();

	// 引用计数打印为2
	cout << ptr1.use_count() << endl;
	cout << ptr2.use_count() << endl;

	return 0;
}

这里简述一下 shared_from_this()的作用吧。(更详细的流程在C++智能指针的enable_shared_from_this和shared_from_this机制)

  • 首先,我们在 enable_shared_from_this<A> 基类中继承一个成员变量 _Wptr,当定义第一个智能指针对象的时候: shared_ptr< A > ptr1(new A()),调用 shared_ptr 的普通构造函数,就会初始化A对象的成员变量 _Wptr,作为观察A对象资源的一个弱智能指针观察者。
  • 其次,我们看一下 shared_from_this()函数,他其实就是直接返回了一个 shared_ptr<_Ty>(_Wptr),该语法在 shared_ptr中也有相应的构造函数,其主要作用就是把一个弱智能指针提升为一个强智能指针,可以在多线程环境中判断对象是否存活或者已经析构释放。
  • 在一个我们可以看到,在整个过程中我们都没有再次使用 shared_ptr 的普通构造函数,也就是说没有在产生额外的引用计数对象,不会存在把一个内存资源,进行多次计数的过程
  • 再次强调,请去阅读施磊老师的博客C++智能指针的enable_shared_from_this和shared_from_this机制
相关推荐
ChoSeitaku10 分钟前
链表循环及差集相关算法题|判断循环双链表是否对称|两循环单链表合并成循环链表|使双向循环链表有序|单循环链表改双向循环链表|两链表的差集(C)
c语言·算法·链表
娅娅梨12 分钟前
C++ 错题本--not found for architecture x86_64 问题
开发语言·c++
DdddJMs__13516 分钟前
C语言 | Leetcode C语言题解之第557题反转字符串中的单词III
c语言·leetcode·题解
兵哥工控17 分钟前
MFC工控项目实例二十九主对话框调用子对话框设定参数值
c++·mfc
汤米粥18 分钟前
小皮PHP连接数据库提示could not find driver
开发语言·php
冰淇淋烤布蕾21 分钟前
EasyExcel使用
java·开发语言·excel
我爱工作&工作love我24 分钟前
1435:【例题3】曲线 一本通 代替三分
c++·算法
拾荒的小海螺27 分钟前
JAVA:探索 EasyExcel 的技术指南
java·开发语言
马剑威(威哥爱编程)1 小时前
哇喔!20种单例模式的实现与变异总结
java·开发语言·单例模式
娃娃丢没有坏心思1 小时前
C++20 概念与约束(2)—— 初识概念与约束
c语言·c++·现代c++