C++八股——智能指针
文章目录
- 1. 背景
- 2. 原理与使用
- 2.1 auto_ptr
- 2.2 unique_ptr
- 2.3 shared_ptr
- 2.4 weak_ptr
- 2.5 定制删除器
1. 背景
智能指针不是指针,是一个管理指针的类,用来存储指向动态分配对象的指针,负责自动释放动态分配的对象,防止堆内存泄漏和悬空指针等问题。
动态分配的资源,交给一个类对象去管理,当类对象声明周期结束时,自动调用析构函数释放资源。
- C++ 98中产生第一个智能指针
auto_ptr - C++ 11起提供三个主要的智能指针,位于
<memory>头文件中:std::unique_ptrstd::shared_ptrstd::weak_ptr
关键特性对比
| 类型 | 所有权 | 复制语义 | 线程安全 | 性能开销 |
|---|---|---|---|---|
unique_ptr | 独占 | 移动转移 | 单线程安全 | 无 |
shared_ptr | 共享 | 允许复制 | 引用计数原子化 | 较高 |
weak_ptr | 无所有权 | 允许复制 | 依赖关联shared | 低 |
2. 原理与使用
智能指针的基本原理是利用RAII,在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在 对象析构的时候释放资源(不需要显式地释放资源)。
如果只是简单地用类封装指针,如:
template<class T>
class Smartptr
{
public:Smartptr(T* ptr) : _ptr(ptr) {}~Smartptr() {delete _ptr;}T& operator*() {return *_ptr;}T* operator->(){return _ptr;}
private:T* _ptr;
};
会出现拷贝问题:用一个智能指针赋值给另一个指针指针时,因为是浅拷贝,会将两个指针指向同一块内存,在程序结束析构智能指针时释放两次空间,导致程序崩溃。
为了解决这个问题,出现以下四类智能指针:
2.1 auto_ptr
原理:
管理权转移,被拷贝对象把资源管理权转移给拷贝对象,导致被拷贝对象悬空。保证了一个资源只有一个对象对其进行管理,这时候一个资源就不会被多个释放。
使用:
auto_ptr<int> ap1(new int(1));
auto_ptr<int> ap2(ap1);
// 此时ap1悬空
模拟实现:
template<class T>
class auto_ptr
{
public:auto_ptr(T* ptr) : _ptr(ptr) {}auto_ptr(const auto_ptr<T>& ap) {_ptr = ap._ptr;ap._ptr = nullptr;}auto_ptr<T>& operator=(const auto_ptr<T>& ap) {// 检测是否自己给自己赋值if(this != &ap) {// 释放当前资源if(_ptr) delete _ptr;_ptr = ap._ptr;ap._ptr = nullptr;}return *this;}~auto_ptr() {delete _ptr;}T& operator*() {return *_ptr;}T* operator->() {return _ptr;}
private:T* _ptr;
};
2.2 unique_ptr
原理:
通过删除拷贝构造函数/赋值运算符来防止拷贝
使用:
unique_ptr<int> up0 = make_unique<int>(0);
unique_ptr<int> up1(new int(1));
unique_ptr<int> up2(new int(2));
sp1 = sp2; // 报错
在函数返回unique_ptr时不要返回其引用:
auto getUnique() {auto ptr = std::make_unique<int>(10);return ptr; // 正确:移动语义转移所有权
}// auto& getUniqueRef() { ... } // 错误:返回引用会导致悬空指针
模拟实现:
template<class T>
class unique_ptr
{
public:unique_ptr(T* ptr) : _ptr(ptr) {}unique_ptr(const unique_ptr<T>& up) = delete;unique_ptr<T>& operator=(const unique_ptr<T>& ap) = delete;~unique_ptr() {delete _ptr;}T& operator*() {return *_ptr;}T* operator->() {return _ptr;}
private:T* _ptr;
};
2.3 shared_ptr
原理:
通过引用计数的方式解决智能指针的拷贝问题:
shared_ptr给每个资源都维护了着一份计数用来记录该份资源被几个对象共享;- 在对象被销毁时(也就是析构函数调用),引用计数减一;
- 如果引用计数是0,就说明自己是最后一个使用该资源的对象,就可以释放该资源;
- 如果引用计数不是0,就说明还有其他对象在使用该份资源,不能释放该资源。
使用:
shared_ptr<int> sp1(new int(1));
cout << sp1.use_count() << endl; // 1
shared_ptr<int> sp2(sp1);
cout << sp2.use_count() << endl; // 2
注意:避免混用裸指针与智能指针
int* raw = new int(5);
std::shared_ptr<int> p1(raw);
std::shared_ptr<int> p2(raw); // 双重释放!
模拟实现:
template<class T>
class shared_ptr
{
public:shared_ptr(T* ptr) : _ptr(ptr), _pcount(new int(1)) {}~shared_ptr(){Release();}shared_ptr(const shared_ptr<T>& sp){_ptr = sp._ptr;_pcount = sp._pcount; ++(*_pcount);}void Release() {if (--(*_pcount) == 0) {delete _pcount;delete _ptr;}}shared_ptr<T>& operator=(const shared_ptr<T>& sp) {//资源地址不一样if (_ptr != sp._ptr) {Release();_pcount = sp._pcount;_ptr = sp._ptr;++(*_pcount);}return *this;}int use_count() {return *_pcount;}// 像指针一样T& operator*() {return *_ptr;}T* operator->() {return _ptr;}T& operator[](size_t pos) {return _ptr[pos];}
private:T* _ptr;int* _pcount;
};
为什么引用计数要用指针,而不用成员变量或者静态成员变量?
-
若将引用计数作为普通成员变量:不同
shared_ptr副本之间无法共享计数(成员变量属于对象,而非资源)。 -
静态变量(
static)的特性:- 属于类本身,所有对象共享同一个静态变量。
- 生命周期与程序一致,无法动态创建或销毁。
- 全局唯一性,无法为每个资源单独维护计数。
这与智能指针的资源独立性要求直接冲突:每个
shared_ptr需要为其管理的资源单独维护引用计数,而静态变量会导致所有资源共享同一个计数器,引发严重错误。如果使用静态变量来计数,以下代码会出现错误:
int main() {int* a = new int(10);int* b = new int(20);BadSharedPtr p1(a); // count=1BadSharedPtr p2(b); // count=2(错误!两个资源共享同一个计数器)p1.~BadSharedPtr(); // count=1(但 a 未被删除)p2.~BadSharedPtr(); // count=0,错误地删除 b,而 a 泄漏!return 0; } // 结果:a 内存泄漏,b 被提前释放,且程序崩溃。
循环引用问题:
class Node;class Parent {
public:std::shared_ptr<Node> child; // Parent 持有 Node 的 shared_ptr~Parent() { std::cout << "Parent destroyed\n"; }
};class Node {
public:std::shared_ptr<Parent> parent; // Node 也持有 Parent 的 shared_ptr(循环引用)~Node() { std::cout << "Node destroyed\n"; }
};int main() {auto parent = std::make_shared<Parent>(); // parent 引用计数 = 1auto node = std::make_shared<Node>(); // node 引用计数 = 1parent->child = node; // node 引用计数 +1 → 2node->parent = parent; // parent 引用计数 +1 → 2return 0;// main 结束时:// parent 引用计数 -1 → 1 → Parent 未被销毁!// node 引用计数 -1 → 1 → Node 未被销毁!
}
2.4 weak_ptr
原理:
观察但不拥有资源,用于解决shared_ptr循环引用问题。
使用:
-
解决
shared_ptr循环引用问题:#include <memory>class Node; // 前置声明class Parent { public:std::shared_ptr<Node> child;~Parent() { std::cout << "Parent destroyed\n"; } };class Node { public:// std::shared_ptr<Parent> parent; // 循环引用导致内存泄漏std::weak_ptr<Parent> parent; // 改用 weak_ptr 解决~Node() { std::cout << "Node destroyed\n"; } };int main() {auto parent = std::make_shared<Parent>(); // parent 引用计数 = 1auto node = std::make_shared<Node>(); // node 引用计数 = 1parent->child = node; // node 引用计数 +1 → 2node->parent = parent; // weak_ptr 不会增加引用计数return 0; // 正确析构// main 结束时:// parent 引用计数 -1 → 0 → Parent 销毁 → child 销毁 → node 引用计数 -1 → 1// node 引用计数 -1 → 0 → Node 销毁! } -
安全访问共享资源:
#include <memory> #include <iostream>class Data { public:void process() { std::cout << "Processing data...\n"; } };int main() {std::shared_ptr<Data> sharedData = std::make_shared<Data>();std::weak_ptr<Data> weakData = sharedData;// 检查 weak_ptr 是否有效if (auto tmp = weakData.lock()) { // 提升为 shared_ptrtmp->process(); // 安全使用std::cout << "Use count: " << tmp.use_count() << "\n"; // 输出 2} else {std::cout << "Data expired\n";}sharedData.reset(); // 释放资源if (weakData.expired()) {std::cout << "Data is no longer available\n";}return 0; }
模拟实现:
template<class T>
class weak_ptr
{
public:weak_ptr() :_ptr(nullptr) {}weak_ptr(const shared_ptr<T>& wp) {_ptr = wp.get();}weak_ptr<T>& operator=(const shared_ptr<T>& wp) {_ptr = wp.get();return *this;}T& operator*() {return *_ptr;}T* operator->() {return _ptr;}
private:T* _ptr;
};
2.5 定制删除器
定制删除器可以解决:如何正确释放用new或者new []开辟的资源。
template<class U, class D> unique_ptr(U* p, D del);
其中Del参数是一个定制删除器,是一个可调用对象,比如函数指针、仿函数、lambda表达式以及被包装器包装后的可调用对象。
// unique_ptr 自定义删除器
auto del = [](int* p) { delete[] p; };
std::unique_ptr<int[], decltype(del)> arr(new int[10], del);
参考:
- C++智能指针详解-CSDN博客
- DeepSeek
相关文章:
C++八股——智能指针
文章目录 1. 背景2. 原理与使用2.1 auto_ptr2.2 unique_ptr2.3 shared_ptr2.4 weak_ptr2.5 定制删除器 1. 背景 智能指针不是指针,是一个管理指针的类,用来存储指向动态分配对象的指针,负责自动释放动态分配的对象,防止堆内存泄漏…...
「华为」人形机器人赛道投资首秀!
温馨提示:运营团队2025年最新原创报告(共210页) —— 正文: 近日,【华为】完成具身智能赛道投资首秀,继续加码人形机器人赛道布局。 2025年3月31日,具身智能机器人头部创企【千寻智能&#x…...
格雷希尔G10和G15系列自动化快速密封连接器,适用于哪些管件的密封,以及它们相关的特性有哪些?
格雷希尔G10和G15系列快速密封连接器,用于自动化和半自动化过程中的外部或内部密封,通过使用气压驱动来挤压内部的密封圈,创造一个适用于各种管件的无泄漏密封连接,连接器内部的弹性密封圈可以提供其他产品不能提供的卓越密封性能…...
mac一键安装gpt-sovit教程中,homebrew卡住不动的问题
mac一键安装gpt-sovit教程 仅作为安装过程中解决homebrew卡住问题的记录 资源地址 https://www.yuque.com/baicaigongchang1145haoyuangong/ib3g1e/znoph9dtetg437xb#mlAoP 下载一键包 下载后并解压,找到install for mac.sh,终端执行bash空格拖拽in…...
专栏特辑丨悬镜浅谈开源风险治理之SBOM与SCA
随着容器、微服务等新技术日新月异,开源软件成为业界主流形态,软件行业快速发展。但同时,软件供应链也越来越趋于复杂化和多样化,软件供应链安全风险不断加剧。 软件供应链安全主要包括软件开发生命周期和软件生存运营周期&#x…...
vue3项目创建-配置-elementPlus导入-路由自动导入
目录 方法一:create-vue 方法二 :Vite Vue Vite.config.ts配置 引入element-plus 安装 如何在项目中使用 Element Plus 完整引入 按需导入 vue3vite中自动配置路由的神器:vite-plugin-pages 1. 安装 2、修改vite.config.js中配置…...
MUSE Pi Pro 编译kernel内核及创建自动化脚本进行环境配置
视频讲解: MUSE Pi Pro 编译kernel内核及创建自动化脚本进行环境配置 今天分享的主题为创建自动化脚本编译MUSE Pi Pro的kernel内核,脚本已经上传到中 GitHub - LitchiCheng/MUSE-Pi-Pro-Learning: MUSE-Pi-Pro-Learning ,有需要可以自行clon…...
Java大师成长计划之第20天:Spring Framework基础
📢 友情提示: 本文由银河易创AI(https://ai.eaigx.com)平台gpt-4o-mini模型辅助创作完成,旨在提供灵感参考与技术分享,文中关键数据、代码与结论建议通过官方渠道验证。 在Java开发领域,Spring …...
Innovus 25.1 版本更新:助力数字后端物理设计新飞跃
在数字后端物理设计领域,每一次工具的更新迭代都可能为项目带来巨大的效率提升与品质优化。今天,就让我们一同聚焦 Innovus 25.1 版本(即 25.10 版本)的更新要点,探寻其中蕴藏的创新能量。 一、核心功能的强势进 AI…...
FastAPI 和 MongoDB 实现请求头参数处理的示例,并在 React 中进行渲染
FastAPI 和 MongoDB 后端 安装必要的库 安装 FastAPI、Uvicorn、Motor(用于 MongoDB 的异步驱动)和 Pydantic(用于数据验证)。 pip install fastapi uvicorn motor pydantic创建 FastAPI 应用 创建一个文件 main.py,并…...
CodeBuddy 中国版 Cursor 实战:Redis+MySQL双引擎驱动〈王者荣耀〉战区排行榜
文章目录 一、引言二、系统架构设计2.1、整体架构概览2.2、数据库设计2.3、后端服务设计 三、实战:从零构建排行榜3.1、开发环境准备3.2、用户与战区 数据管理3.2.1、MySQL 数据库表创建3.2.2、实现用户和战区数据的 CURD 操作 3.3、实时分数更新3.4、排行榜查询3.5…...
码蹄集——分解、数组最大公约数、孪生质数、卡罗尔数、阶乘数
MT1158 分解 输入正整数N和M,判断N是否可以分解成M个不同的正整数的和,输出YES或者NO。 格式 输入格式:输入正整数N和M,空格分隔 输出格式:输出YES或者NO 样例 1 输入:5 2 输出:YES 思路…...
【React中函数组件和类组件区别】
在 React 中,函数组件和类组件是两种构建组件的方式,它们在多个方面存在区别,以下详细介绍: 1. 语法和定义 类组件:使用 ES6 的类(class)语法定义,继承自 React.Component。需要通过 this.props 来访问传递给组件的属性(props),并且通常要实现 render 方法返回 JSX…...
Idea Code Templates配置
Templates配置 配置位置模板案例 配置位置 Settings->Editor->File and Code Templates模板案例 #if (${PACKAGE_NAME} && ${PACKAGE_NAME} ! "")package ${PACKAGE_NAME};#endimport com.ktools.common.dataprocess.DataProcess; import com.ktools…...
在线SQL转ER图工具
在线SQL转ER图网站 在数据库设计、软件开发或学术研究中,ER图(实体-关系图) 是展示数据库结构的重要工具。然而,手动绘制ER图不仅耗时费力,还容易出错。今天,我将为大家推荐一款非常实用的在线工具——SQL…...
python高级特性
json.dumps({a:1,n:2}) #Python 字典类型转换为 JSON 对象。相当于jsonify data2 json.loads(json_str)#将 JSON 对象转换为 Python 字典 异步编程:在异步编程中,程序可以启动一个长时间运行的任务,然后继续执行其他任务,而无需等…...
汇编:子程序设计
一、 实验要求 实验目的: 熟练掌握算术运算汇编指令的使用熟练掌握子程序设计的基本方法熟练掌握程序的调试方法 实验内容: 编程实现两个数:#8888H和#79H的乘除运算结合实验1的代码,将加减乘除四则运算写成四个子程序ÿ…...
从概念表达到安全验证:智能驾驶功能迎来系统性规范
随着辅助驾驶事故频发,监管机制正在迅速补位。面对能力表达、使用责任、功能部署等方面的新要求,行业开始重估技术边界与验证能力,数字样机正成为企业合规落地的重要抓手。 2025年以来,围绕智能驾驶功能的争议不断升级。多起因辅…...
ubuntu 24.04 error: cannot uninstall blinker 1.7.0, record file not found. hint
最近在打python3.12的镜像,安装browser-gym的核心库,编译一个使用browswer agents的环境,然后出现了下面的问题: error: cannot uninstall blinker 1.7.0, record file not found. hint: the package was installed by debian.系…...
DeepSeek基于注意力模型的可控图像生成
DeepSeek大模型高性能核心技术与多模态融合开发 - 商品搜索 - 京东 图像的加噪与模型训练 在扩散模型的训练过程中,首先需要对输入的信号进行加噪处理,经典的加噪过程是在图像进行向量化处理后在其中添加正态分布,而正态分布的值也是与时间…...
“端 - 边 - 云”三级智能协同平台的理论建构与技术实现
摘要 随着低空经济与智能制造的深度融合,传统集中式云计算架构在实时性、隐私保护和资源效率上的瓶颈日益凸显。本文提出“端 - 边 - 云”三级智能协同平台架构,以“时空 - 资源 - 服务”三维协同理论为核心,构建覆盖终端感知、边缘计算、云端…...
AI时代,如何实现人机共舞?
在科技飞速发展的当下,人工智能(AI)已不再是科幻作品中的遥远想象,而是深入渗透到我们生活与工作的方方面面。从智能手机中的语音助手,到金融领域的风险预测模型;从医疗影像的智能诊断,到工业生…...
component :is是什么?
问: component :is是什么? 是组件? 那我们是不是就不需要自己创建组件了?还是什么意思?component :is和什么功能是类似的,同时和类似功能相比对什么时候用component :is…...
2025 3D工业相机选型及推荐
3D工业相机是专门为工业应用设计的三维视觉采集设备,能够获取物体的三维空间信息,在智能制造、质量检测、机器人引导等领域有广泛应用。 一、主要类型 1.结构光3D相机 通过投射特定光斑或条纹图案并分析变形来重建三维形状 典型代表:双目结构…...
OceanBase 在业务监控系统中的应用实践
本文作者来自于一家总部在宁波的新能源上市公司,公司业务包括光伏新能源产品的研发与产销。 作为年产值达百亿的企业,监控系统是不可或缺的IT管理体系之一,对于确保业务连续性及预警风险非常重要。2022年,公司选择把Zabbix作为企业…...
每日Prompt:品牌化键盘键帽
提示词 一个超逼真的3D渲染图,展示了四个机械键盘键帽,排列成紧密的2x2网格,所有键帽相互接触。从等轴测角度观察。一个键帽是透明的,上面用红色印刷着“{just}”字样。另外三个键帽采用颜色:{黑色、紫色和白色}。一个…...
RabbitMQ发布订阅模式深度解析与实践指南
目录 RabbitMQ发布订阅模式深度解析与实践指南1. 发布订阅模式核心原理1.1 消息分发模型1.2 核心组件对比 2. 交换机类型详解2.1 交换机类型矩阵2.2 消息生命周期 3. 案例分析与实现案例1:基础广播消息系统案例2:分级日志处理系统案例3:分布式…...
超声波传感器模块
欢迎来到 破晓的历程的 博客 ⛺️不负时光,不负己✈️ 文章目录 1.HC-SR04介绍2.HC-SR04原理介绍2.1原理概述3.2原理详解 4驱动代码编写4.1写前思考4.2硬件连线 5.总结hcsr04.hhcsr04.c 1.HC-SR04介绍 超声波传感器有很多种类的型号:HC-SR04、UC-025、…...
LeetCode 513 找树左下角的值 LeetCode 112 路径总和 LeetCode106 从中序与后序遍历序列构造二叉树
LeetCode 513 找树左下角的值 迭代法——层序遍历 思路:对树进行层序遍历操作,层序遍历完后,输出树最后一层的第一个节点。 # Definition for a binary tree node. # class TreeNode(object): # def __init__(self, val0, leftNone, r…...
Docker常见问题全攻略:从安装到优化
常见Docker安装问题及解决方案 系统兼容性问题排查安装过程中权限不足的解决方法网络配置问题导致安装失败的修复 系统兼容性问题排查 Docker在安装过程中可能会遇到系统兼容性问题,尤其是在较旧的操作系统或特定硬件架构上。确保操作系统版本符合Docker的最低要…...
