IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 109-智能指针(带引用计数的智能指针) -> 正文阅读

[C++知识库]109-智能指针(带引用计数的智能指针)

1、智能指针(带引用计数的智能指针)

shared_ptr和weak_ptr;

带引用计数的好处: 多个智能指针可以管理同一个资源。

带引用计数的智能指针是怎么解决多个指针持有一个资源的?

  • 带引用计数: 给每一个对象的资源,匹配一个引用计数
  • 当1个智能指针引用这个资源的时候,这个资源相应的引用计数就加1,当这个智能指针出作用域,不再使用这个资源的时候,这个资源的引用计数就减1。
  • 当引用计数减1不为0的时候,这个智能指针不使用这个资源了,但是还有其他智能指针在使用这个资源,这个智能指针不能析构这个资源,只能直接走人。
  • 当引用计数减1为0的时候,说明当前智能指针是最后使用这个资源的智能指针,所以它要负责这个资源的释放。(完美的解决了智能指针的浅拷贝—就是多个智能指针多次释放同一个资源)

1.1、模拟实现shared_ptr

引用计数类的实现:
在这里插入图片描述
然后我们给之前定义的智能指针,添加这个成员
在这里插入图片描述

拷贝构造:
在这里插入图片描述
赋值重载:
在这里插入图片描述
在这里插入图片描述
检查自己原先指向的资源,因为自己要改变指向了,如果执行delRef后,引用计数为0的话,就释放资源;
在这里插入图片描述
main函数中:
在这里插入图片描述
在这里插入图片描述
注意:

上面的CSmartPtr和标准的shared_ptr的区别是:

  1. 引用计数的加减无法在多线程的情况下保证安全;
  2. shared_ptr将m_count定义成atomic_int类型,原子整形类,通过CAS来保证++和–的线程安全的。

库中的shared_ptr和weak_ptr都是线程安全的,可以直接使用在多线程的环境下。

#include <iostream>
#include <memory>
using namespace std;


//对资源进行引用计数的类
template<typename T>
class RefCnt
{
public:
	RefCnt(T* ptr = nullptr)
		:mptr(ptr)
	{
		if (mptr != nullptr)
		{
			mcount = 1;
		}
	}
	void addRef() { mcount++; }		//增加资源的引用计数
	int  defRef() { return --mcount; }
	void show() { cout << mcount << endl; }
private:
	T* mptr;
	atomic_int mcount;
};

template<typename T>
class CSmartPtr
{
public:
	CSmartPtr(T* p = nullptr)
		:ptr(p)
	{
		mpRefCnt = new RefCnt<T>(ptr);
	}
	~CSmartPtr()
	{
		if (0 == mpRefCnt->defRef())
		{
			delete ptr;
			ptr = nullptr;
		}
	}
	T& operator*()
	{
		return *ptr;
	}
	T* operator->()
	{
		return ptr;
	}
	void Count() { return mpRefCnt->show(); }

	CSmartPtr(const CSmartPtr<T>& src)
		:ptr(src.ptr), mpRefCnt(src.mpRefCnt)
	{
		if (ptr != nullptr)
		{
			mpRefCnt->addRef();
		}
	}
	CSmartPtr<T>& operator=(const CSmartPtr<T>& src)
	{
		if (this == &src)
		{
			return *this;
		}
		if (0 == mpRefCnt->defRef())
		{
			delete ptr;
		}
		ptr = src.ptr;
		mpRefCnt = src.mpRefCnt;
		mpRefCnt->addRef();
		return *this;
	}

private:
	T* ptr;		//指向资源的指针
	RefCnt<T>* mpRefCnt;	//指向该资源引用计数的类
};

int main()
{
	CSmartPtr<int>p(new int(50));
	CSmartPtr<int>q(p);
	cout << *p << endl;
	cout << *q << endl;
	p.Count();
	cout << p.operator->() << endl;

}

1.2、shared_ptr的交叉引用的问题

在这里插入图片描述
弱智能指针观察强智能指针,强智能指针观察资源(内存)
在这里插入图片描述
强智能指针循环引用(交叉引用)是什么问题?什么结果?怎么解决?
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
但是如果我们这么调用呢?
在这里插入图片描述
在这里插入图片描述

  • 出main函数作用域,pb先析构(将B资源的引用计数从2减为1),再析构pa(将A资源的引用计数从2减为1),此后,AB对象的引用计数均为1,对象不能析构;
  • 造成对象new出来的资源无法释放,严重的资源泄漏问题。

在这里插入图片描述
在这里插入图片描述
定义对象的时候,使用强智能指针shared_ptr:
在这里插入图片描述
引用对象时,使用弱智能指针weak_ptr:
在这里插入图片描述
运行成功:
在这里插入图片描述
完全没有问题。

弱智能指针只是观察作用,查看对象是否活着,不会造成引用计数改变。
在这里插入图片描述
在这里插入图片描述
可以正常释放资源!


假如说,现在A里面有一个好的方法:
在这里插入图片描述
在这里插入图片描述

  • 在A类中有一个testA函数,B类中弱智能指针是无法调用A类的testA函数的;

  • 弱智能指针只会观察资源,不能使用资源;

  • 弱智能指针没有提供*->运算符重载,不能将弱智能指针当成裸指针看待。

在这里插入图片描述
怎么解决弱指针指针不能调用函数的问题?

  • 将弱智能指针提升!使用lock()进行提升,提升之后它也是一个强智能指针。
    在这里插入图片描述
  • 在多线程中,弱智能指针观察的资源有可能被释放,有可能没有被释放,主要就是根据引用计数是否为0;
  • 弱智能指针需要使用对象,需要从一个观察者提升为强智能指针,在提升的过程中有可能提升失败,资源已经释放了,有可能提升成功,资源还没释放。

在main函数中调用:
在这里插入图片描述
在这里插入图片描述

class B;
class A
{
public:
	A() { cout << "A()构造" << endl; }
	~A() { cout << "~A()析构" << endl; }
	weak_ptr<B>ptr2;
	void testA() { cout << "非常好的方法" << endl; }
};
class B
{
public:
	B() { cout << "B()构造" << endl; }
	~B() { cout << "~B()析构" << endl; }
	void func()
	{
		shared_ptr<A>ps = ptr1.lock();
		if (ps != nullptr)
		{
			ps->testA();
		}
		cout << ps.use_count() << endl;//智能指针ps提升成功,引用技术加1到2
		//智能指针ps出函数作用域自动析构,引用计数从2减到1
	}
	weak_ptr<A>ptr1;
};
int main()
{
	shared_ptr<A>ptra(new A());
	shared_ptr<B>ptrb(new B());
	ptra->ptr2 = ptrb;
	ptrb->ptr1 = ptra;
	cout << ptra.use_count() << endl;//1
	cout << ptra.use_count() << endl;//1
	ptrb->func();
	cout << ptra.use_count() << endl;//1
	cout << ptra.use_count() << endl;//1
	ptrb->func();
}

1.3、多线程访问共享对象的线程安全问题

在这里插入图片描述
在这里插入图片描述
直接模拟 多线程访问共享对象线程安全问题。
在这里插入图片描述

添加代码:

  • 主线程先睡上2s再释放资源。

在这里插入图片描述
运行结果:

  • 代码正常;

在这里插入图片描述
修改代码:

  • 将子线程进行睡眠2s再进行调用函数,此时在main线程中,就已经将p指针释放了;非常不合理

在这里插入图片描述
解决方法:

  • 我们在q访问A对象的时候,需要侦测一下A对象是否存活;使用强弱智能指针;

对象被释放,检测出来问题:
在这里插入图片描述
智能指针出作用域就会将引用计数-1,在这里会释放A资源;
在这里插入图片描述
在这里插入图片描述
正常访问的情况:

  • 将子线程的睡眠2s的代码放到主线程的作用域中,这样,资源A就不会释放了;子线程就能够正常访问了。

在这里插入图片描述
在这里插入图片描述
强弱智能指针在线程中,通过对象的资源计数 来 监测对象的生存状态。

1.4、自定义删除器

在这里插入图片描述

  • 并不是说所有资源的释放都是delete指针 进行释放的。
  • 比如说,用智能指针来托管数组的资源,delete就得加个中括号[]了,
  • 如果用智能指针管理的是文件资源,或者是其他资源,释放的方式不是delete。

在这里插入图片描述
这2个智能指针都可以提供自定义删除器。

系统的智能指针的析构函数是一个函数的调用;
在这里插入图片描述

举例1—数组资源的自定义释放

  • 智能指针管理一个数组资源,使用自定义的删除器Mydeletor删除资源。
    在这里插入图片描述
    使用了自定义的删除器:
    在这里插入图片描述

举例2—文件资源的自定义释放

在这里插入图片描述
在这里插入图片描述
先构造的后析构,后构造的先析构

问题: 上面自定义的删除器虽然能用,但是很麻烦,能不能直接在定义智能指针的语句中指定自定义的删除器?

使用lamda表达式:

c++11对标之前c++中的 函数对象 ,lamda表达式非常灵活;

lamda表达式对象的类型如何确定?

function函数对象类型,可以留下lamda表达式的类型
在这里插入图片描述
在这里插入图片描述

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-05-02 13:24:33  更:2022-05-02 13:24:40 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 22:00:43-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码