C++进阶(12)智能指针

news2024/9/24 13:16:24

个人主页:仍有未知等待探索-CSDN博客

专题分栏:C++

一、概述

智能指针在构造的时候开辟空间,当智能指针生命周期结束则会自动调用析构函数释放空间。

解决问题:对于new开辟的时候出现异常,导致之前开辟的空间没有手动释放,内存泄露。

头文件:<memory>

智能指针有三种:auto_ptrunique_ptrshared_ptr

二、理解

1、RAII

一种用对象生命周期来控制程序资源的技术。

在构造对象的时候获取资源,在析构对象的时候释放资源

2、智能指针的原理

  • RAII技术
  • 重载 * 和 -> 运算符

不同智能指针之前的区别:

特点auto_ptrunique_ptrshared_ptr
所有权独占,可转移独享,不可复制但可移动共享,通过引用计数
数组管理不支持支持(通过特化版本)支持(通过特化版本)
安全性较低,已被弃用较高,推荐使用较高,但需注意循环引用
线程安全不适用(已被弃用)依赖于具体实现,但通常不是线程安全的(对对象的访问需要同步)引用计数操作是线程安全的,但对象访问需要同步

3、auto_ptr 

auto_ptr:管理权转移,被拷贝对象悬空,有风险。

不建议用!!!

auto_ptr<int> sp1(new int(1));
auto_ptr<int[]> ap2(new int[3]); // 报错,不支持数组管理
auto_ptr<int> sp2(sp1) // 转移资源,而不是拷贝构造
// 有坑
// sp1是左值,但是移动资源了

4、unique_ptr

  • 不支持拷贝,支持数组管理
  • 线程一般不安全。
//auto_ptr<int> ap1(new int(3));
//auto_ptr<int[]> ap2(new int[3]);

// 支持数组管理
unique_ptr<int[]> up1(new int[3]);
// 普通创建
unique_ptr<int> up2(new int(1));

unique_ptr<A> up3(new A());
// 不支持拷贝和赋值
unique_ptr<A> up4(up3);
// 支持移动拷贝和移动赋值
unique_ptr<A> up5(move(up3));

5、shared_ptr

  • 不支持拷贝,支持数组管理。
  • 引用计数操作是线程安全的,但是对对象访问需要同步。

注意:

1、构造智能指针建议用make_shared,这样的话会减少内存碎片化。

2、两个线程拷贝只能指针要++计数、智能指针对象析构也要--计数。所以要保证引用计数的线程安全。

3、智能指针对象本身拷贝析构是线程安全的,底层引用计数加减是线程安全的,指向的资源访问不是线程安全的。

缺陷:

1、循环引用

shared_ptr的缺陷:循环引用。--- 释放逻辑是个死循环。如下是循环引用的场景。

对p1的析构依赖于p2,对于p2的析构依赖于p1。

struct node
{
    shared_ptr<node> prev;
    shared_ptr<node> next;
    ~node()
    {
        cout << "~node" << endl;
    }
};
int main()
{
    shared_ptr<node> p1(new node);
    shared_ptr<node> p2(new node);
    p1->next = p2;
    p2->prev = p1;
    return 0;
}

这样写就没有循环引用的问题了。

struct node
{
    weak_ptr<node> prev;
    weak_ptr<node> next;
    ~node()
    {
        cout << "~node" << endl;
    }
};
int main()
{
    shared_ptr<node> p1(new node);
    shared_ptr<node> p2(new node);
    p1->next = p2;
    p2->prev = p1;
    return 0;
}

2、析构出错

当shared_ptr指向的是malloc开辟的空间,析构的时候就会出错。原因是shared_ptr底层释放空间是用的delete。

shared_ptr<A[]> p(new A[10]);
--------------------------------------------
遇到下面开辟空间方式的话,底层的delete可能会崩溃,
所以要有一个定制删除器。
shared_ptr<int> p((int*)malloc(4));
shared_ptr<FILE> p(fopen("test.txt","r"'));

所以需要用到一个定制删除器。

shared_ptr<int> p((int*)malloc(4), [](A*ptr){free(ptr);});
shared_ptr<FILE> p(fopen("test.txt","r"'), [](FILE* ptr){fclose(ptr);}

6、weak_ptr

weak_ptr的出现主要是为了解决shared_ptr循环引用的问题。

解决方案:

  • weak_ptr不支持RAII,不单独管理。
  • 赋值、拷贝不增加计数引用的次数,只指向资源。
  • expired函数:通过查看技术引用是否为零,来判断weak_ptr是否过期

7、定制删除器 

定制删除器也是为了解决shared_ptr在析构时候的缺陷。

我们需要在shared_ptr构造的时候传入一个定制删除器即可。

定制删除器:是一个可调用对象(函数指针、仿函数对象、lambda表达式)。        

三、模拟实现shared_ptr 

#include <iostream>
#include <functional>
#include <mutex>
using namespace std;

template<class T>
class SharedPtr
{
private:
	T* _p;
	int* _refcount;
	mutex* _mtx;
	function<void(T*)> _del;
public:
	SharedPtr(T* p = nullptr)
		:_p(p)
		,_refcount(new int(1))
		,_mtx(new mutex)
	{}
	template <class D>
	SharedPtr(T* p = nullptr, D del = [](T* ptr) {delete ptr; })
		: _p(p)
		, _refcount(new int(1))
		, _mtx(new mutex)
		, _del(del)
	{}
	SharedPtr(const SharedPtr& p)
		:_p(p._p)
		,_refcount(p._refcount)
		,_mtx(p._mtx)
	{
		add_refcount();
	}
	~SharedPtr()
	{
		release();
	}
	SharedPtr& operator=(SharedPtr& p)
	{
		if (&p == this) return *this;

		release();

		_p = p._p;
		_refcount = p._refcount;
		
		add_refcount();
		return *this;
	}
	T& operator*()
	{
		return *_p;
	}
	T* operator->()
	{
		return _p;
	}
	int use_count()
	{
		return *_refcount;
	}

private:
	void add_refcount()
	{
		lock_guard<mutex> lg(*_mtx);
		(*_refcount)++;
	}
	void release()
	{
		lock_guard<mutex> lg(*_mtx);
		(*_refcount)--;
		if (0 == _refcount)
		{
			_del(_p);
			delete _refcount;
		}
	}
};

struct A
{
	int _a;
};
int main()
{
	/*SharedPtr<int> sp1(new int(1));
	SharedPtr<int> sp2(sp1);

	SharedPtr<int> sp3;
	sp3 = sp2;

	cout << sp1.use_count() << endl;
	cout << sp2.use_count() << endl;
	cout << sp3.use_count() << endl;*/

	SharedPtr<A> sp1((A*)malloc(sizeof(A)), [](A* ptr) { free(ptr); });

	return 0;
}

谢谢支持!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1963911.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

VMware虚拟机安装Windows7教程(超详细)

目录 1. 下载2. 安装 VMware3. 安装 Window73.1 新建虚拟机3.2 安装操作系统 4. 设置共享文件夹5. 安装 VMware Tools5.1 下载&安装缺少驱动5.2 开始安装 VMware Tools 6. 未&#x1f414;&#x1f525;解决 创作不易&#xff0c;禁止转载抄袭&#xff01;&#xff01;&…

MyBatis代码生成器:SpringBoot 引入MybatisGenerator

1. 引入插件 <plugin><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generator-maven-plugin</artifactId><version>1.3.5</version><configuration><!--generator配置⽂件所在位置--><configurati…

7.5 grafana上导入模板看图并讲解告警

本节重点介绍 : blackbox_exporter grafana大盘导入和查看告警配置讲解 grafana大盘 grafana 上导入 blackbox_exporter dashboard 地址 https://grafana.com/grafana/dashboards/13659举例图片http总览图value_mapping设置 展示设置阈值&#xff0c;展示不同背景色 告警配…

过滤和筛选树形结构数据

场景 在平时项目开发中经常会遇到树形数据的处理&#xff0c;如树形数据根据条件值过滤掉不符合条件的选项&#xff0c;如果是最后的子数据符合条件那么就会保存这条树形链路的所有直属数据并过滤掉所有非直属的数据。如果是符合条件的数据还有子元素&#xff0c;那么就保留所…

算法强训day18

一、压缩字符串 链接&#xff1a;压缩字符串(一)_牛客题霸_牛客网 #include<iostream> using namespace std; #include<vector> class Solution { public:/*** 代码中的类名、方法名、参数名已经指定&#xff0c;请勿修改&#xff0c;直接返回方法规定的值即可***…

mac电脑不能快速传输文件的原因是什么 mac无法拷贝文件到移动硬盘的原因是什么 macbook传输速度慢

新买的移动硬盘连接上Mac电脑&#xff0c;想要将Mac上的文件拷贝到移动硬盘里&#xff0c;但是Mac无法拷贝文件到移动硬盘里&#xff0c;直接拖拽、剪切都不行&#xff0c;尤其是一些大的安装包或视频文件的拷贝&#xff0c;需要花费大量的时间&#xff0c;给Mac用户造成了很多…

Excel文档受损打不开,还能修复吗?

Excel作为最常用的表格文件&#xff0c;在我们日常的工作当中使用尤其频繁&#xff0c;且经常涉及到一些重要数据文件的编辑和保存。然而&#xff0c;有时我们会遇到Excel文档受损而无法打开的情况&#xff0c;这无疑会给我们的工作带来诸多不便。那么&#xff0c;当Excel文档受…

SpringCloud API网关

SpringCloud API网关 文章目录 SpringCloud API网关1. 概念2. Spring Cloud Gateway2.1 介绍2.2 操作方式 3.Route Predicate Factories3.1 介绍3.2 使用方式 1. 概念 API网关&#xff0c;简称网关&#xff0c;本身是一个服务&#xff0c;通常作为后端服务的唯一入口&#xff…

git学习准备阶段

准备阶段 ubantu下载安装git sudo apt-get install git查看git版本 git -v注册用户名 git config --global user.name [name][name]填入自己的名字&#xff0c;如果没有空格的情况下&#xff0c;可以不加引号,–global是在全局下操作&#xff0c;如果没有这个参数就只是在本…

Orcale(备份导入导出)

1.备份恢复 1.1.备份定义 备份就是把数据库复制到转储设备的过程。其中&#xff0c;转储设备是指用于放置数据库副本的磁带或磁盘。通常也将存放于转储设备中的数据库的副本称为原数据库的备份或转储。备份是一份数据副本 1.2.备份分类 从物理与逻辑的角度来分类&#xff1a…

C++ 哈希系列容器 + 位图 + 布隆过滤器

目录 1 unordered 系列关联式容器 2 哈希介绍 3 闭散列哈希 4 哈希桶 5 封装实现unordered系列set和map 6 位图 7 哈希切割 8 布隆过滤器 1 unordered 系列关联式容器 在学习哈希结构实现之前&#xff0c;我们先学习一下哈希在库里面的一些使用unordered_set 和unorderen_m…

昂科烧录器支持HolyChip芯圣电子的8位触摸微控制器HC88T3661

芯片烧录行业领导者-昂科技术近日发布最新的烧录软件更新及新增支持的芯片型号列表&#xff0c;其中HolyChip芯圣电子的8位触摸微控制器HC88T3661已经被昂科的通用烧录平台AP8000所支持。 HC88T3661是一颗采用高速低功耗CMOS工艺设计开发的增强型8位触摸微控制器&#xff0c;内…

探索全光网技术 | 全光网络技术方案选型建议一 (办公室场景)

全光网技术方案选型建议 | 办公室场景 目录 一、场景设计需求二、办公室场景拓扑三、部署方式四、产品相关规格说明五、方案优势与特点 校园办公室网络是校园员工日常处理工作的重要载体&#xff0c;学校领导、教学、研究、行政、财务等各部门均需要办公室网络来承载日常的工作…

【已修改 Python】TypeError: ‘tuple’ object does not support item assignment

【已修改 Python】TypeError: ‘tuple’ object does not support item assignment 在Python编程的浩瀚宇宙中&#xff0c;TypeError: tuple object does not support item assignment 是一个令人困惑但又常见的错误。它如幽灵般潜伏在代码的深处&#xff0c;当你不慎尝试修改…

C#:枚举及位标志周边知识详解(小白入门)

文章目录 枚举为什么要有枚举?枚举的性质设置默认类型和显式设置成员的值 位标志(重要)位标记是什么及作用位标志周边知识HasFlag判断是否有该功能枚举前面加Flags的好处 关于枚举的更多知识using static简化代码获取枚举成员的字面量 枚举 为什么要有枚举? 为了增加代码的…

WPF中使用定时器更新元素-DispatcherTimer

在WPF中使用定时器来更新UI元素是一种常见且有用的做法&#xff0c;特别是当你需要基于时间间隔来刷新数据或执行某些操作时。DispatcherTimer是WPF中用于在UI线程上执行周期性任务的理想选择&#xff0c;因为它确保了对UI元素的更新是线程安全的 例子程序 每隔0.5s 界面中的…

volatile的使用方法介绍

volatile的使用方法介绍 volatile的使用方法介绍 文章目录 volatile的使用方法介绍volatile的使用方法 volatile的使用方法 volatile的使用大家最熟悉的就是说防止编译器优化&#xff0c;今天我们来做一下实验 //vs2022 中volatile的修饰 #include<Windows.h> #includ…

vue3集成LuckySheet实现导入本地Excel进行在线编辑

第一步&#xff1a;克隆或者下载下面的代码 git clone https://github.com/dream-num/Luckysheet.git第二步&#xff1a;安装依赖 npm install npm install gulp -g 第三步&#xff1a;运行 npm run dev效果如下图所示 第四步&#xff1a;打包 打包执行成功后&#xff0c;…

40 元组与列表的异同点

① 列表和元组都属于有序序列&#xff0c;都支持双向索引访问其中的元素&#xff0c; 以及使用 count() 方法统计元素的出现次数和 index() 方法获取元素的索引&#xff0c;len()、map()、filter()等大量内置函数和 、*、、in 等运算符也都可以作用于列表和元组。虽然列表和元组…

互联网扭蛋机小程序,行业的发展前景

近年来&#xff0c;扭蛋机在市场上的影响力逐渐增加&#xff0c;受到了消费者的欢迎&#xff0c;发展前景非常广阔。目前&#xff0c;扭蛋机市场迎来了线上发展趋势&#xff0c;消费者也开始热衷于在线上参与扭蛋&#xff01;扭蛋机小程序的发展&#xff0c;也为行业带来了更大…