C++11 智能指针:unique_ptr、shared_ptr和weak_ptr 功能特性 模拟实现
文章目录
- unique_ptr
- 功能和特性
- 使用场景
- make_unique
- 模拟实现
- shared_ptr
- 功能和特性
- 使用场景
- make_shared
- 模拟实现
- weak_ptr
C++ 中智能指针都是 RAII(Resource Acquisition Is Initialization)机制的典型应用,在构造时获取资源,在析构时释放资源,将资源管理与对象的生命周期绑定,使得资源管理更加直观和可靠。
unique_ptr
std::unique_ptr 的主要作用是对动态分配的资源进行严格的独占式管理,确保在其生命周期结束时自动释放所管理的资源,从而防止内存泄漏,让资源的生命周期与 std::unique_ptr 变量的生命周期紧密绑定,无需手动调用 delete 操作符,使资源管理更安全、更高效。
功能和特性
- 独占所有权:同一时刻只能有一个
std::unique_ptr指向给定的资源,保证了资源的独占访问,避免多个指针同时操作同一资源带来的冲突和数据不一致问题。 - 移动语义:支持移动构造和移动赋值操作,允许将资源的所有权从一个
std::unique_ptr转移到另一个std::unique_ptr,但不支持复制操作,提高了资源转移的效率,避免了不必要的资源复制。 - 自动释放资源:当
std::unique_ptr对象超出作用域或被显式销毁时,会自动调用其析构函数来释放所管理的资源,无需手动释放,降低了忘记释放资源导致内存泄漏的风险。 - 可定制删除器:可以通过模板参数指定自定义的删除器,用于定义释放资源的特定方式,以满足不同资源类型的特殊释放需求。
- 空指针值:可以通过默认构造函数创建一个空的
std::unique_ptr,表示不管理任何资源,也可以通过将其赋值为nullptr来使其变为空指针。
使用场景
- 函数内部局部资源管理:在函数内部动态分配资源时,使用
std::unique_ptr来管理这些资源,确保函数执行结束时资源能被正确释放,例如在函数中动态分配数组或对象。 - 资源独占场景:当某个资源只需要被一个对象独占使用时,如一个文件操作类中使用
std::unique_ptr来管理文件句柄,保证同一时间只有该对象能访问文件。 - 作为函数返回值:函数可以返回一个
std::unique_ptr,将动态分配资源的所有权安全地转移给调用者,调用者可以继续管理该资源,而无需担心资源的释放问题。 - 管理动态分配的数组:可以使用
std::unique_ptr来管理动态分配的数组,通过指定数组删除器来确保数组内存的正确释放。
make_unique
std::make_unique 是 C++14 引入的一个辅助函数,用于创建并返回 std::unique_ptr 对象。它在动态分配内存时,提供了更简洁和安全的方式,与 std::unique_ptr 的构造配合使用,减少了潜在的错误风险。
| 特点 | std::make_unique | std::unique_ptr和new |
|---|---|---|
| 内存分配 | 高效,单一操作分配内存 | 初始化和分配是分离的操作 |
| 异常安全 | 更安全,避免资源泄漏 | 可能因异常导致内存泄漏 |
| 代码简洁性 | 更简洁,无需手动使用 new | 手动使用 new,代码更冗长 |
| 自定义删除器 | 无法直接使用自定义删除器 | 支持自定义删除器 |
示例
int main() {// 使用 unique_ptr 管理一个 vector<int>auto vecPtr = std::make_unique<std::vector<int>>();vecPtr->push_back(10);vecPtr->push_back(20);vecPtr->push_back(30);std::cout << "Vector elements: ";for (const auto& elem : *vecPtr) {std::cout << elem << " ";}std::cout << "\n";// vecPtr2 超出作用域时,资源会自动释放return 0;
}
模拟实现
template <typename T>
class UniquePtr {
public:explicit UniquePtr(T* ptr = nullptr) : ptr_(ptr) {}UniquePtr(const UniquePtr&) = delete;UniquePtr<T>& operator=(const UniquePtr&) = delete;UniquePtr(UniquePtr&& other) noexcept : ptr_(other.ptr_) {other.ptr_ = nullptr;}UniquePtr& operator=(UniquePtr&& other) noexcept {if (this != &other) {delete ptr_;ptr_ = other.ptr_;other.ptr_ = nullptr;}return *this;}~UniquePtr() { delete ptr_; }T& operator*() { return *ptr_; }T* operator->() { return ptr_; }// 获取原始指针T* get() const { return ptr_; }// 重置指针void reset(T* ptr = nullptr) {if (ptr != ptr_) {delete ptr_;ptr_ = ptr;}}// 释放所有权T* release() {T* tmp = ptr_;ptr_ = nullptr;return tmp;}private:T* ptr_;
};
shared_ptr
std::shared_ptr 的主要作用是实现资源的共享所有权。多个 std::shared_ptr 可以指向同一个对象,通过引用计数机制来跟踪有多少个 std::shared_ptr 共享该对象。当最后一个指向该对象的 std::shared_ptr 被销毁或重置时,对象的内存会被自动释放,从而避免了内存泄漏。
std::shared_ptr 可能会导致循环引用问题,即两个或多个 std::shared_ptr 相互引用,使得引用计数永远不会变为 0,从而导致内存泄漏。为了解决这个问题,可以使用 std::weak_ptr,它是一种弱引用,不会增加引用计数。
功能和特性
- 引用计数:
std::shared_ptr内部维护一个引用计数,记录有多少个std::shared_ptr共享同一个对象。每当一个新的std::shared_ptr指向该对象时,引用计数加 1;当一个std::shared_ptr被销毁或重置时,引用计数减 1。当引用计数变为 0 时,对象的内存会被自动释放。 - 共享所有权:多个
std::shared_ptr可以同时拥有同一个对象的所有权,这使得资源可以在多个地方被安全地使用,而不用担心资源过早释放或重复释放的问题。 - 自动资源管理:
std::shared_ptr会在引用计数变为 0 时自动释放所管理的资源,无需手动调用delete操作符,提高了代码的安全性和可维护性。 - 可复制和赋值:
std::shared_ptr支持复制构造和赋值操作,复制或赋值操作会增加引用计数,确保资源的共享和正确管理。 - 自定义删除器:可以通过模板参数指定自定义的删除器,用于定义释放资源的特定方式,以满足不同资源类型的特殊释放需求。
使用场景
- 多个对象共享资源:当多个对象需要同时访问和使用同一个资源时,使用
std::shared_ptr可以方便地实现资源的共享。例如,多个线程可能需要访问同一个数据结构,使用std::shared_ptr可以确保该数据结构在所有线程都不再使用时才被释放。 - 实现对象池:在对象池的实现中,
std::shared_ptr可以用于管理对象的生命周期。当对象从对象池中取出时,使用std::shared_ptr管理该对象;当对象被放回对象池或不再使用时,std::shared_ptr会自动释放对象的内存。
make_shared
std::make_shared 是 C++11 中引入的一个用于创建 std::shared_ptr 对象的函数模板。它通过单次内存分配同时创建被管理的对象和控制块(control block),从而提高效率并减少潜在的内存碎片。
| 特点 | std::make_shared | shared_ptr和new |
|---|---|---|
| 内存分配 | 一次分配,控制块和对象共享内存。 | 两次分配,控制块和对象分开存储。 |
| 异常安全 | 构造失败无内存泄漏(更安全)。 | 构造失败时可能导致对象泄漏。 |
| 代码简洁性 | 简洁,无需显式 new。 | 需要显式使用 new,易出错。 |
| 自定义删除器 | 不支持自定义删除器。 | 支持自定义删除器(适合特殊资源管理)。 |
auto sp = std::make_shared<int>(9); // 同时分配对象和控制块
模拟实现
template <typename T>
class SharedPtr {
public:explicit SharedPtr(T* ptr = nullptr) : ptr_(ptr), ref_count_(ptr ? new std::atomic<size_t>(1) : nullptr) {}SharedPtr(const SharedPtr& other) : ptr_(other.ptr_), ref_count_(other.ref_count_) {if (ref_count_)++(*ref_count_);}SharedPtr& operator=(const SharedPtr& other) {if (this != other) {release();ptr_ = other.ptr_;ref_count_ = other.ref_count_;if (ref_count_)ref_count_->fetch_a;}return *this;}T& operator*() { return *ptr_; }T* operator->() { return ptr_; }~SharedPtr() { release(); }T* get() const { return ptr_; }int use_count() const {return ref_count_ ? ref_count_->load() : 0;}private:void release() {if (ref_count_ && ref_count_->fetch_sub(1) == 0) {delete ptr_;delete ref_count_;}}T* ptr_;std::atomic<size_t>* ref_count_;
};
weak_ptr
std::weak_ptr 是 C++ 标准库中的一种智能指针,它是为了配合 std::shared_ptr 而引入的,用于解决 std::shared_ptr 可能存在的循环引用。当多个 std::shared_ptr 相互引用形成循环时,它们的引用计数永远不会降为 0,导致对象无法被释放,而 std::weak_ptr 不影响引用计数,可作为一种弱引用解决此问题。
#include <iostream>
#include <memory>class B; // 前向声明class A {
public:std::shared_ptr<B> b_ptr;~A() { std::cout << "A destroyed\n"; }
};class B {
public:std::weak_ptr<A> a_ptr; // 使用 weak_ptr 避免循环引用~B() { std::cout << "B destroyed\n"; }
};int main() {auto a = std::make_shared<A>();auto b = std::make_shared<B>();a->b_ptr = b;b->a_ptr = a;// 此时 a 和 b 的引用计数都为 1,不会造成循环引用std::cout << "a use_count: " << a.use_count() << "\n"; // 输出 1std::cout << "b use_count: " << b.use_count() << "\n"; // 输出 1return 0;
}
相关文章:
C++11 智能指针:unique_ptr、shared_ptr和weak_ptr 功能特性 模拟实现
文章目录 unique_ptr功能和特性使用场景make_unique模拟实现 shared_ptr功能和特性使用场景make_shared模拟实现 weak_ptr C 中智能指针都是 RAII(Resource Acquisition Is Initialization)机制的典型应用,在构造时获取资源,在析构…...
Spring Boot启动过程?
目录 1. 启动入口 2. SpringApplication 初始化 3. 准备环境 4. 创建应用上下文(ApplicationContext) 5. 准备应用上下文 6. 刷新应用上下文 7. 启动 Web 服务器(若为 Web 应用) 8. 发布 ApplicationStartedEvent 事件 9. 执行 Runner 10. 发布 ApplicationReady…...
2025年软考报名费用是多少?全国费用汇总!
软考报名时间终于确定了!想要参加2025年软考的同学们注意啦!特别是那些一年只有一次考试机会的科目,千万不要错过哦!这里为大家整理了各地的报名时间、科目、费用等信息,快来看看吧! 一、2025年软考时间安…...
算法-二叉树篇06-二叉树的最大深度
二叉树的最大深度 力扣题目链接 题目描述 给定一个二叉树 root ,返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。 解题思路 一样可以使用递归的思想,代码也十分简洁,计算出两个子树的深度取最大加…...
git merge -s ours ...的使用方法
当我们在自己的feature branch上开发时,并且已经commit,push了好几次 同时develop分支也commit , push了好几次, 如下图所示 这个时候就不能直接将feature branch上的改动 pull request到develop上面,因为develop基线已经不一样了…...
面试之《react hooks在源码中是怎么实现的?》
要深入理解 React Hooks 在源码中的实现,可以从以下几个关键方面来剖析: 核心数据结构 在 React 内部,使用链表来管理每个函数组件的 Hooks。每个 Hook 对应一个节点,这些节点通过 next 指针相连。以下是简化后的 Hook 节点结构…...
数字可调控开关电源设计(论文+源码)
1 设计要求 在本次数字可调控开关电源设计过程中,对关键参数设定如下: (1)输入电压:DC24-26V,输出电压:12-24(可调); (2)输出电压误差…...
【DeepSeek】【GPT-Academic】:DeepSeek集成到GPT-Academic(官方+第三方)
目录 1 官方deepseek 1.1 拉取学术GPT项目 1.2 安装依赖 1.3 修改配置文件中的DEEPSEEK_API_KEY 2 第三方API 2.1 修改DEEPSEEK_API_KEY 2.2 修改CUSTOM_API_KEY_PATTERM 2.3 地址重定向 2.4 修改模型参数 2.5 成功调用 2.6 尝试添加一个deepseek-r1参数 3 使用千帆…...
DeepSeek R1 + 飞书机器人实现AI智能助手
效果 TFChat项目地址 https://github.com/fish2018/TFChat 腾讯大模型知识引擎用的是DeepSeek R1,项目为sanic和redis实现,利用httpx异步处理流式响应,同时使用buffer来避免频繁调用飞书接口更新卡片的网络耗时。为了进一步减少网络IO消耗&…...
Android移动应用开发实践-1-下载安装和简单使用Android Studio 3.5.2版本(频频出错)
一、下载安装 1.Android Studio3.5.2下载地址:Android Studio3.5.2下载地址 其他版本下载地址:其他版本下载地址 2.安装教程(可以多找几个看看) 安装 | 手把手教你Android studio 3.5.2安装(安装教程)_a…...
Rk3568驱动开发_驱动编写和挂载_2
1.字符驱动介绍: 字符驱动:按照字节流镜像读写操作的设备,读写数据分先后顺序,例如:点灯、按键、IIC、SPI、等等都是字符设备,这些设备的驱动叫字符驱动设备 Linux应用层如何调用驱动: 字符设…...
验证码识别:使用OCR技术识别图形验证码详解
文章目录 一、基本原理二、所需工具2.1 Python环境2.2 图像处理库2.3 OCR引擎2.4 Python接口 三、实现步骤3.1 获取验证码图像3.2 图像预处理3.3 使用OCR进行字符识别3.4 基本 OCR 识别样例 四、提高识别准确率的方法4.1 字符分割4.2 使用深度学习模型4.3 数据增强4.4 集成多个…...
剑指 Offer II 033. 变位词组
comments: true edit_url: https://github.com/doocs/leetcode/edit/main/lcof2/%E5%89%91%E6%8C%87%20Offer%20II%20033.%20%E5%8F%98%E4%BD%8D%E8%AF%8D%E7%BB%84/README.md 剑指 Offer II 033. 变位词组 题目描述 给定一个字符串数组 strs ,将 变位词 组合在一起…...
【苍穹外卖】问题笔记
【DAY1 】 1.VCS找不到 好吧,发现没安git 接着发现安全模式有问题,点开代码信任此项目 2.导入初始文件,全员爆红 好像没maven,配一个 并在设置里设置好maven 3.启用注解,见新手苍穹 pom.xml改lombok版本为1.1…...
微信小程序 - 自定义实现分页功能
概述 在微信小程序项目中,没有现成的分页器组件,所以需要自定义实现分页功能 自定义实现分页功能 1、index.json {"usingComponents": {"van-button": "vant/weapp/button/index"} }这里使用 Vant Weapp 中的 van-butt…...
1.1部署es:9200
安装es:root用户: 1.布署java环境 - 所有节点 wget https://d6.injdk.cn/oraclejdk/8/jdk-8u341-linux-x64.rpm yum localinstall jdk-8u341-linux-x64.rpm -y java -version 2.下载安装elasticsearch - 所有节点 wget ftp://10.3.148.254/Note/Elk/…...
《模拟器过检测教程:Nox、雷电、Mumu、逍遥模拟器 Magisk、LSposed 框架安装与隐藏应用配置》
一、夜神模拟器 (Nox) 过检测 使用版本:7.0.6.2(20250209) 1. 准备工作 将需要用到的应用放入文件夹: C:\Users\Administrator.DESKTOP-I5V50SS\Nox_share\Download 2. 安装面具鸭(Magisk) 在模拟器下…...
人工智能、机器学习、深度学习和大语言模型之间的关系
人工智能(AI)、机器学习(ML)、深度学习(DL)和大语言模型(LLM)之间是逐层包含且技术递进的关系,具体如下: 1. 层级关系 人工智能(AI)…...
上传securecmd失败
上传securecmd失败 问题描述:KES V8R6部署工具中,节点管理里新建节点下一步提示上传securecmd失败,如下: 解决办法: [rootlocalhost ~]# yum install -y unzip 上传的过程中会解压,如果未安装unzip依赖包…...
C++:dfs,bfs各两则
1.木棒 167. 木棒 - AcWing题库 乔治拿来一组等长的木棒,将它们随机地砍断,使得每一节木棍的长度都不超过 5050 个长度单位。 然后他又想把这些木棍恢复到为裁截前的状态,但忘记了初始时有多少木棒以及木棒的初始长度。 请你设计一个程序…...
Python在实际工作中的运用-通用格式CSV文件自动转换XLSX
继续上篇《Python在实际工作中的运用-CSV无损转XLSX的几个方法》我们虽然对特定格式的CSV实现了快速转换XLSX的目标,但是在运行Py脚本前,还是需要编辑表格创建脚本和数据插入脚本,自动化程度很低,实用性不强,为减少人工提高效率,实现输入CSV文件路径即可自动适配完成转换…...
P9420 [蓝桥杯 2023 国 B] 子 2023
P9420 [蓝桥杯 2023 国 B] 子 2023 题目 分析代码 题目 分析 刚拿到这道题,我大脑简单算了一下,这个值太大了,直观感觉就很难!! 但是,你仔仔细细的一看,先从最简单的第一步入手,再…...
2025-02-26 学习记录--C/C++-C语言 判断字符串S2是否在字符串S1中
合抱之木,生于毫末;九层之台,起于累土;千里之行,始于足下。💪🏻 C语言 判断字符串S2是否在字符串S1中 #include <stdio.h> // 引入标准输入输出库,用于使用 printf 等函数 #…...
004 Kafka异常处理
6.异常处理 文章目录 6.异常处理1.异常分类与处理原则2.生产者异常处理1. 同步发送捕获异常2. 异步发送回调处理 3.消费者异常处理1.全局异常处理器2.方法级处理3.重试yml配置 4.死信队列(DLQ)配置1. 启用死信队列2. 手动发送到DLQ 5.事务场景异常处理1.…...
创建第一个 Maven 项目(二)
六、添加依赖 在 Maven 项目开发过程中,添加依赖是一项常见且关键的操作。通过添加依赖,我们可以引入项目所需的各种库和框架,极大地扩展项目的功能。接下来,我们将以 JUnit 依赖为例,详细介绍如何在 Maven 项目中添加…...
游戏引擎学习第124天
仓库:https://gitee.com/mrxiao_com/2d_game_3 回顾/复习 今天是继续完善和调试多线程的任务队列。之前的几天,我们已经介绍了多线程的一些基础知识,包括如何创建工作队列以及如何在线程中处理任务。今天,重点是解决那些我们之前没有注意到…...
组件的组成和组件的嵌套关系
组件的组成 首先建一个.vue文件,在里面写一个内容: <template> <div><div class"container">{{ message }}</div> </div> </template> <script> export default{data(){return{message:"组件…...
2025 PHP授权系统网站源码
2025 PHP授权系统网站源码 安装教程: PHP7.0以上 先上传源码到服务器,然后再配置伪静态, 访问域名根据操作完成安装, 然后配置伪静态规则。 Ngix伪静态规则: location / { if (!-e $request_filename) { rewrite …...
KIMI K1.5:大规模强化学习在大语言模型中的应用与工程实践
目录 1、核心技术创新:长上下文强化学习 2、策略优化的技术细节 2.1、在线镜像下降变体 2.2、长度惩罚机制 2.3、智能采样策略 3、工程架构创新 3.1、混合部署框架 3.2、代码沙箱与奖励模型 3.3、分布式系统架构 4、实验成果与性能提升 5、结论与未来展望 大语言模…...
Linux MySQL 8.0.29 忽略表名大小写配置
Linux MySQL 8.0.29 忽略表名大小写配置 问题背景解决方案遇到的问题: 问题背景 突然发现有个大写的表报不存在。 在Windows上,MySQL是默认支持忽略大小写的。 这个时候你要查询一下是不是没有配置: SHOW VARIABLES LIKE lower_case_table…...
