C++智能指针及其应用
C++11之后出现了 shared_ptr 和 unique_ptr,这两个类都是基于RAII技术进行设计的
RAII
利用对象生命周期来控制程序资源(如内存,文件句柄,网络连接,互斥量等资源)的技术,具体地说,就是通过构造函数获得资源,通过析构函数释放资源。
RAII的思想需要考虑到一个事实:一个资源不能被释放两次,那么此时如果有两个对象管理同一块资源,这两个对象前后销毁,分别调用析构函数,将导致运行错误。
针对上述可能出现的错误,C++11中有两种方案
方案一
unique_ptr 不允许拷贝或赋值,设计时直接禁用拷贝构造函数和赋值重载函数
unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;
方案二
在有一些场景中,两个智能指针需要管理同一个资源,此时是通过 shared_ptr 进行实现的
shared_ptr 通过引用计数确定管理一块资源的实例化对象的个数。
引用计数达到的效果:只让最后一个管理资源的对象执行释放资源的逻辑
但是,引用计数带来了很多问题,下面将把这些问题提出,并给出shared_ptr解决问题的设计方案
引用计数的设计方案思考
首先,引用计数不能设计成 shared_ptr 的局部私有成员。如果设计成私有成员,拷贝构造和赋值重载改变的时,无法保证旧对象和新对象同时改变引用计数。
其次,引用计数不能设计成全局变量或者静态bianilin,否则在如下场景中 sp1的_pcount和sp2的_pcount的有着相同地址,这是错误的
class A{
public:A(int a1):_a1(a1){}int _a1 = 1;
};int main(){shared_ptr<A> sp1(new A(1));shared_ptr<A> sp2(new A(2));
}
最后,应该设计一个整型指针变量作为引用计数的变量,而且应该在堆上开辟一块空间,不然这个变量的初始化很麻烦
引用计数带来的问题
记 shared_ptr 中引用计数这个成员变量的名称为 _pcount, 即 int* _pcount;
问题一
在多线程场景下,_pcount 作为一个临界资源,应该如何考虑其线程安全问题?
_pcount作为临界资源的场景:
void func(std::shared_ptr<list<int>> sp, int n){for (int i = 0; i < n; i++){std::shared_ptr<list<int>> copy(sp);sp->emplace_back(i);}
}int main(){std::shared_ptr<list<int>> sp1(new list<int>);thread t1(func, sp1, 1000000);thread t2(func, sp1, 2000000);t1.join();t2.join();
}
从如上代码可以看到,t1 和 t2 这两个线程同时进行同时对 sp 进行尾插,
问题二
在调用赋值重载时,应该如何改变旧对象和新对象中的引用计数?(注意需考虑到如下场景)
class A{
public:A(int a):_a(a){}int _a;
};int main(){std::shared_ptr<A> sp1(new A(1));sp1 = sp1;
}
问题三
循环引用场景应该怎么处理?
循环引用场景如下:
struct Node{std::shared_ptr<Node> _next;std::shared_ptr<Node> _prev;int _val;~Node(){/*析构函数调用时的逻辑*/}
};int main(){std::shared_ptr<Node> p1(new Node);std::shared_ptr<Node> p2(new Node);p1->_next = p2;p2->_next = p1;
}
该场景带来的问题描述:
由于p1->_next的存在,p2 这个对象想要析构,起码要等到 p1 的生命周期结束,因为只有p1的生命周期结束,才会调用其析构函数来释放p1->_next,让引用计数做减减,而由于p2->_next的存在,p1 这个对象想要析构,起码要等到 p2 的生命周期结束,因为只有p2的生命周期结束,才会调用其析构函数来释放p2->_next,
由此看来,p1 和 p2 都只能等对方先析构才能析构,那么最终的结果会导致这两者都不析构
问题四
shared_ptr所管理的资源释放的方式不同,应该怎么设计,使得不同类型的被管理资源都可以通过对应的释放方式进行释放?
考虑如上问题之后,可以设计出如下的 sharedPtr类
#pragma once
#include <atomic>
#include <functional>
using namespace std;template<class T>
class sharedPtr{
public:sharedPtr(T* sharedptr):_shared_ptr(sharedptr),_pcount(new atomic<int>(1)) // 问题一{}template<class D> // 问题四sharedPtr(T* sharedptr, D delMethod):_shared_ptr(sharedptr),_pcount(new atomic<int>(1)) // 问题一,_del(delMethod){}sharedPtr(const sharedPtr<T>& sp):_shared_ptr(sp._shared_ptr),_pcount(sp._pcount){++(*_pcount);}// 问题二,只有左操作数和右操作数的管理的指针不同才进行引用计数的更改// 旧对象的引用计数减一,减到零就释放sharedPtr<T>& operator=(const sharedPtr<T>& sp) {if (_shared_ptr != sp._shared_ptr) {this->release();_shared_ptr = sp._shared_ptr;_pcount = sp._pcount;++(*_pcount);}return *this;}void release() {if (--(*_pcount) == 0) {_del(_shared_ptr); // 问题四delete _pcount;}}int use_count() {return *_pcount;}T* operator->() {return _shared_ptr;}T& operator*() {return *_shared_ptr;}~sharedPtr() {release();}
private:T* _shared_ptr;atomic<int>* _pcount;// 问题四function<void(T*)> _del = [](T* ptr) {delete ptr; };
};
循环引用的解决方案
从以上的分析可以发现,问题三并没有解决,只能通过weak_ptr进行解决,weak_ptr 不属于RAII技术,创建时不会增加引用计数
make_shared解决的问题
当调用以下代码时
auto ptr = std::shared_ptr<MyClass>(new MyClass(args...));
可能会引起内存碎片,中途异常等问题,因为这句代码会被拆成两句话
MyClass* rawPtr = new MyClass(args...);
auto ptr = std::shared_ptr<MyClass> (rawPtr);
由于引用计数和 MyClass 对象在栈上申请的空间不连续,可能引起内存碎片问题,而MyClass在调用构造函数先开辟空间,开辟空间成功但执行逻辑的过程中如果抛异常,将会导致内存泄漏,make_shared 就解决了这个问题
智能指针的应用
如果出现以下场景,则可以使用智能指针:
需要在栈上开辟一段空间,存放一个自定义类型的结构体
注意:这里不考虑这个自定义类型在调用构造函数时是否需要再在栈上申请一段空间
以下是工厂模式的样例代码
#include <iostream>
#include <string>
#include <memory>class Fruit{
public:Fruit(){}virtual void name() = 0;
};class Apple : public Fruit{
public:Apple(){}void name() override{std::cout << "我是苹果!" << std::endl;}
};class Banana : public Fruit{
public:Banana(){}void name() override{std::cout << "我是香蕉!" << std::endl;}
};class FruitFactory{
public:virtual std::shared_ptr<Fruit> create() = 0;
};class AppleFactory : public FruitFactory{
public:std::shared_ptr<Fruit> create() override{return std::make_shared<Apple>();}
};class BananaFactory : public FruitFactory{
public:std::shared_ptr<Fruit> create() override{return std::make_shared<Banana>();}
};int main(){// 先创建一个一个指向FruitFactory的指针// 虽然这里不能创建抽象类的实例,但是可以创建指向抽象类的指针// 这里创建的是一个指向AppleFactory类型的实例的智能指针对象,这个智能指针的类型是FruitFactory// tmp作为一个类型为FruitFactory的指针,可以触发多态,// 不同类中的create将创建出指向不同类型对象的fruitstd::shared_ptr<FruitFactory> tmp(new AppleFactory());std::shared_ptr<Fruit> fruit = tmp->create();fruit->name();tmp.reset(new BananaFactory());fruit = tmp->create();fruit->name();
}
相关文章:

C++智能指针及其应用
C11之后出现了 shared_ptr 和 unique_ptr,这两个类都是基于RAII技术进行设计的 RAII 利用对象生命周期来控制程序资源(如内存,文件句柄,网络连接,互斥量等资源)的技术,具体地说,就是…...

06 算法基础:算法的定义、表现形式(自然语言、伪代码、流程图)、五个特性(有穷性、确定性、可行性、输入、输出)、好算法的设计目标
目录 1 算法的定义 2 算法的三种表现形式 2.1 自然语言 2.2 伪代码 2.3 流程图 3 算法的五个特性 3.1 有穷性 3.2 确定性 3.3 可行性 3.4 输入 3.5 输出 4 好算法的设计目标 4.1 正确性 4.2 可读性 4.3 健壮性 4.4 通用性 4.5 高效率与低存储量 1 算法的定义 …...

【红外传感器】STM32C8T6标准库使用红外对管
好好学习,天天向上 前言一、了解红外二、标准库的代码1.infrared.c2.infrared.h3.main.c4 现象 总结 前言 红外线:频率介于微波与可见光之间的电磁波。 参考如下 【STM32】标准库与HAL库对照学习教程外设篇–红外避障传感器 光电红外传感器详解&#…...

STM32L010F4 最小系统设计
画一个 STM32L010F4 的测试板子...... by 矜辰所致前言 最近需要用到一个新的 MCU: STM32L010F4 ,上次测试的 VL53L0X 需要移植到这个芯片上,网上一搜 STM32L010F4,都是介绍资料,没有最小系统,使用说明等。…...

AI 工具大赏:探索智能时代的得力助手
在当今这个科技飞速发展的时代,人工智能(AI)技术已经深入到我们生活的方方面面。从日常办公到创意设计,从学术研究到娱乐休闲,AI 工具正以其强大的功能和便捷的使用体验,成为人们不可或缺的得力助手。那么&…...

安通物流借助CRM重塑企业客户关系管理新格局
安通控股股份有限公司(以下简称"安通控股")是一家扎根集装箱多式联运物流产业的现代综合物流服务企业,致力于为客户提供绿色、经济、高效、安全的集装箱全程物流解决方案。 据Alphaliner排名统计,截至2023年10月,安通控股综合运力全球排名21位,位居国内内贸集装箱物…...

C++标准模板库--vector
vector 介绍 vector(向量)是一种序列容器,表示为可以改变大小的数组。vector中的元素使用连续的存储位置,这意味着也可以使用指向其元素的常规指针偏移量来访问任意元素,且与数组一样高效。但与数组不同的是ÿ…...

通信学习干货:运营商为什么要大力推广FTTR?
随着数字化时代的来临,互联网的需求不断增长,家庭网络也在不断演进。光纤到家(FTTH)已经成为提供高速互联网连接的标配,但随着技术的发展,我们迎来了FTTR(光纤到房间)技术࿰…...

【Spring篇】初识之Spring的入门程序及控制反转与依赖注入
🧸安清h:个人主页 🎥个人专栏:【计算机网络】,【Mybatis篇】 🚦作者简介:一个有趣爱睡觉的intp,期待和更多人分享自己所学知识的真诚大学生。 文章目录 🎯初始Spring …...

OpenLayers:构建高质量的Web地图应用
OpenLayers:构建高质量的Web地图应用 文章目录 OpenLayers:构建高质量的Web地图应用简介为什么选择 OpenLayers?跨平台兼容性高性能渲染高度可定制化社区支持 安装与设置功能扩展矢量图层地理编码投影转换 交互与事件其他高级特性控制动画数据…...

Java比较两个Excel是否内容一致
领导每天让比较两个Excel中的内容,为了节省工作效率多摸鱼,就写了个java接口,通过上传两个文件 进行代码比较得到详细的比较结果(这个需要自己根据日志二开) 目前只实现了比较功能 话不多说直接上代码,具体看注释 package com.yx…...

UniApp入门教程
UniApp X 是一种用于构建跨平台应用程序的框架,它基于 Vue.js 并通过 UniApp 技术栈支持多种平台,如微信小程序、支付宝小程序、H5、Android 和 iOS。以下是 UniApp X 的一些关键特点和基础知识: UniApp X 的特点 跨平台支持: 可…...

Vue.js中使用Element UI实现动态表单项管理及验证
在Vue.js项目中,表单是与用户交互的重要部分,特别是在需要动态管理表单项的场景下,如何优雅地实现添加、删除、上移、下移及验证功能变得尤为重要。本文将详细介绍如何使用Element UI来实现一个包含动态表单项管理以及验证功能的表单。 效果…...

一插U盘就提示格式化?原因、恢复与预防全攻略
一、现象直击:U盘插入电脑即提示格式化 在日常的工作与生活中,U盘作为重要的数据存储和传输工具,被广泛应用于各类场景。然而,有时当我们满怀期待地将U盘插入电脑时,却会遭遇一个令人头疼的问题——系统弹出提示框&am…...

云电脑使用教程标准版
云电脑,也称为云桌面,是一种通过互联网连接远程服务器,使用虚拟桌面环境来执行计算任务的技术。川翔云电脑通过创建软件镜像,让用户能够快速启动并使用预配置的软件和资料,提供高效且经济的云服务。相较于公有云服务&a…...

浏览器服务端文件下载控制(安全阻止、文件浏览器打开还是下载行为控制)
文章目录 简介Chrome已阻止不安全内容下载PDF直接打开txt、xml、js文件被自动打开了而不是下载阿里OSS设置response header阿里OSS修改metadata 简介 随着浏览器的发展,有很多安全方面的限制,对我们的文件下载行为产生了很大的影响。 在JavaScript下载…...

机器学习——量子机器学习
量子机器学习: 未来的机器学习方法 量子计算和机器学习的结合为计算科学带来了前所未有的前景。量子机器学习(QML)正在迅速发展,目标是利用量子计算的优势来处理传统计算机无法高效解决的问题。本文将深入探讨量子机器学习的基本概念、量子计算的关键技术、具体的量…...

[Linux] 创建可以免密登录的SFTP用户
本文主要包含: 创建新用户创建密钥对用于免密登录新用户将新建用户改造为SFTP用户为SFTP上传数据设置限速 1. 创建新用户 sudo useradd sftp_user sudo passwd sftp_user # 输入密码2. 创建密钥对 参考这篇文章 [Linux] 生成 PEM 密钥对实现服务器的免密登录 3. 将新建用户…...

【部署篇】Redis-03主从模式部署(源码方式安装)
一、准备主机 主从模式只是解决了数据备份容灾并不能解决单点故障问题,生产环境中需要在主从模式基础上增加哨兵,实现主节点宕机时自动将其中一个重节点设置为新的主节点。 主机IP角色说明192.168.128.31master,主节点可读写。192.168.128…...

C/C++语言基础--C++四大类型转换讲解
本专栏目的 更新C/C的基础语法,包括C的一些新特性 前言 通过前面几节课,我们学习了抽象、封装、继承、多态、异常等概念,这一篇我们将继续学习C的类型转换,和C语言还有很大区别的;在本节课最后,也简要说…...

KafKa 集群【docker compose】
文章目录 主机准备部署编辑 docker-compose.ymlcontrollerbroker生成cluster_id 一篇完整的 docker-compose.yml 文件查看集群状态使用 kafka-ui 查看拉取 kafka-ui添加集群查看集群状态 使用命令行查看 配置讲解controllerbroker 主机准备 IPcontroller idbroker id192.168.1…...

【工具篇】MLU运行XInference部署手册
文章目录 前言一、平台环境准备二、代码下载三、安装部署1.正常pip 安装 四、运行结果展示1.如果界面404或没有东西请这样做2.运行效果 前言 Xorbits Inference(Xinference)是一个功能强大、用途广泛的库,旨在为语言、语音识别和多模态模型提…...

计算机网络:数据链路层 —— 扩展共享式以太网
文章目录 共享式以太网共享式以太网存在的问题在物理层扩展以太网扩展站点与集线器之间的距离扩展共享式以太网的覆盖范围和站点数量 在链路层扩展以太网网桥的主要结构网桥的基本工作原理透明网桥自学习和转发帧生成树协议STP 共享式以太网 共享式以太网是当今局域网中广泛采…...

平安养老险深圳分公司:创新养老服务,深入践行金融为民
党的二十届三中全会《决定》提出:“积极发展科技金融、绿色金融、普惠金融、养老金融、数字金融,加强对重大战略、重点领域、薄弱环节的优质金融服务。” 为经济社会发展提供高质量服务,更好满足人民日益增长的美好生活需要,金融…...

静态站点生成器哪家强?
有一种方法,让你写好文档后,快速地让同事、用户和合作伙伴看到,这就是静态站点生成器。 静态站点生成器是一种软件,用于创建不需要服务器端脚本的网站。这些网站由纯HTML文件组成,可能还包括CSS和JavaScript来增强功…...

从0开始部署优化虚拟机
一,vm workstation 安装 CentOS-7 忽略 二、查看虚拟机IP ip address 得到 192.168.196.128/24 宿主机进行Ping测试 C:\Users\Administrator>ping 192.168.196.128正在 Ping 192.168.196.128 具有 32 字节的数据: 来自 192.168.196.128 的回复: 字节32 时间…...

录屏有道, 四款必备录屏工具推荐!
制作教程视频、游戏直播或是远程会议记录等都需要录屏,那么到底应该怎么录屏呢?接下来就给大家介绍几个好用的录屏工具 Foxit REC 直达链接:www.foxitsoftware.cn/REC/ 操作教程:立即获取 Foxit REC以其强大的功能、简洁的界面…...

5G NR:UE初始接入信令流程浅介
UE初始接入信令流程 流程说明 用户设备(UE)向gNB-DU发送RRCSetupRequest消息。gNB-DU 包含 RRC 消息,如果 UE 被接纳,则在 INITIAL UL RRC MESSAGE TRANSFER 消息中包括为 UE 分配的低层配置,并将其传输到 gNB-CU。IN…...

探索 Spring AI:Java 开发者的 AI 应用开发新利器
在当今这个由人工智能驱动的时代,AI 技术正在以前所未有的速度改变着我们的工作和生活方式。对于 Java 开发者来说,将 AI 能力集成到他们的应用程序中,已经成为了一个迫切的需求。阿里云开源的 Spring AI Alibaba 框架,正是为了满…...

Linux历史
Linux 于 1991 年由芬兰学生 Linus Torvalds 作为个人项目开始,旨在创建一个新的免费操作系统内核。在其历史发展中,Linux 内核经历了持续的增长。自 1991 年首次发布源代码以来,Linux 内核从少量的 C 语言文件,且受限于禁止商业发…...