c++源码阅读__smart_ptr__正文阅读
文章目录
- 简介
- 源码
- 解析
- 1. 引用计数的实现方式
- 2. deleter静态方法的赋值时间节点
- 3.make_smart的实现方式 与 好处
- 4. 几种构造函数
- 4.1 空构造函数
- 4.2 接收指针的构造函数
- 4.3 接收指针和删除方法的构造函数 , 以及auto进行模板lambda的编写
- 4.4 拷贝构造函数
- 4.5 赋值运算符
- 5. release函数, 指针的delete 和 设置为nullptr
- 6. 获取内部变量, 指针和引用
简介
git地址: shimachao/smart_ptr说明: 这是一个仿写shared_ptr的库, 只有简短的200行, 实现了shared_ptr的大部分功能, 上手简单, 非常适合新手阅读.本文说明: 由于本项目没有那么多难点, 就不再像上篇开源代码阅读中c++源码阅读__ThreadPool__正文阅读一样, 不单独开一个前提知识部分了, 由于本项目比较简单, 大家就当作对shared_ptr的一次复习. 看一乐呵就行.
源码
此代码在c++20环境下直接可以运行, 源码如下
#pragma once#include <functional>// 模仿shared_ptr实现一个智能指针
template <typename T>
class smart_ptr
{
public:smart_ptr();explicit smart_ptr(T*);smart_ptr(const smart_ptr&);smart_ptr(T*, std::function<void(T*)>);smart_ptr& operator=(const smart_ptr&);T& operator*() const;T* operator->() const;~smart_ptr();// 向bool的类型转换explicit operator bool() const;bool unique();void reset();void reset(T*);void reset(T*, std::function<void(T*)>);T* release();T* get() const;private:// 默认的deleterstatic std::function<void(T*)> default_del;private:unsigned* m_p_use_count = nullptr;T* m_pobject = nullptr;std::function<void(T*)> m_del = default_del;
};template <typename T>
std::function<void(T*)> smart_ptr<T>::default_del = [](T*p) {delete p; p = nullptr; };template <typename T, typename... Args>
smart_ptr<T> make_smart(Args&&... args)
{smart_ptr<T> sp(new T(std::forward<Args>(args)...));return sp;
}template <typename T>
smart_ptr<T>::smart_ptr():m_pobject(nullptr), m_p_use_count(new unsigned(1))
{
}template <typename T>
smart_ptr<T>::smart_ptr(T *p):m_pobject(p), m_p_use_count(new unsigned(1))
{
}template <typename T>
smart_ptr<T>::smart_ptr(T *p, std::function<void(T*)> del):m_pobject(p), m_p_use_count(new unsigned(1)), m_del(del)
{
}template <typename T>
smart_ptr<T>::smart_ptr(const smart_ptr& rhs):m_pobject(rhs.m_pobject), m_p_use_count(rhs.m_p_use_count), m_del(rhs.m_del)
{(*m_p_use_count)++;
}template <typename T>
smart_ptr<T>& smart_ptr<T>::operator =(const smart_ptr &rhs)
{// 使用rhs的deleterm_del = rhs.m_del;// 递增右侧运算对象的引用计数++(*rhs.m_p_use_count);// 递减本对象的引用计数if (--(*m_p_use_count) == 0){// 如果管理的对象没有其他用户了,则释放对象分配的成员m_del(m_pobject);delete m_p_use_count;}m_p_use_count = rhs.m_p_use_count;m_pobject = rhs.m_pobject;return *this; // 返回本对象
}template <typename T>
T& smart_ptr<T>::operator*() const
{return *m_pobject;
}template <typename T>
T* smart_ptr<T>::operator->() const
{return &this->operator*();
}template <typename T>
smart_ptr<T>::~smart_ptr()
{if (--(*m_p_use_count) == 0){m_del(m_pobject);m_pobject = nullptr;delete m_p_use_count;m_p_use_count = nullptr;}
}template <typename T>
bool smart_ptr<T>::unique()
{return *m_p_use_count == 1;
}template <typename T>
void smart_ptr<T>::reset()
{(*m_p_use_count)--;if (*m_p_use_count == 0){m_del(m_pobject);}m_pobject = nullptr;*m_p_use_count = 1;m_del = default_del;
}template <typename T>
void smart_ptr<T>::reset(T* p)
{(*m_p_use_count)--;if (*m_p_use_count == 0){m_del(m_pobject);}m_pobject = p;*m_p_use_count = 1;m_del = default_del;
}template <typename T>
void smart_ptr<T>::reset(T *p, std::function<void(T*)> del)
{reset(p);m_del = del;
}template <typename T>
T* smart_ptr<T>::release()
{(*m_p_use_count)--;if (*m_p_use_count == 0){*m_p_use_count = 1;}auto p = m_pobject;m_pobject = nullptr;return p;
}template <typename T>
T* smart_ptr<T>::get() const
{return m_pobject;
}template <typename T>
smart_ptr<T>::operator bool() const
{return m_pobject != nullptr;
}
解析
这里会把smart_ptr类中每一部分都单独拿出来说明, 并进行举例
1. 引用计数的实现方式
我们可以看到他的实现方式
unsigned* m_p_use_count = nullptr;
使用的是整数的指针, 如果不使用指针的话, 那么是打不到公用引用计数的效果的, 如下
int* a1 = new int(1);
int* a2 = a1;
*a2 += 1;
cout << *a1 << endl;
cout << *a2 << endl;int a3 = 1;
int a4 = a3;
a3 += 1;
cout << a4 << endl;
cout << a4 << endl;int a5 = 1;
int& a6 = a5;
a5++;
cout << a5 << endl;
cout << a6 << endl;
执行结果

那么我们是不是也可以用引用&来实现引用计数的共用呢? 我觉得是可以的
2. deleter静态方法的赋值时间节点
template <typename T>
class smart_ptr
{
private:// 默认的deleterstatic std::function<void(T*)> default_del;std::function<void(T*)> m_del = default_del;
}
template <typename T>
std::function<void(T*)> smart_ptr<T>::default_del = [](T*p) {delete p; p = nullptr; };
静态方法的赋值, 是 程序启动并进入主函数之前进行赋值的, 具体地这个初始化是在包含这行代码的翻译单元被加载时完成的, 所以 default_del在任何smart_ptr的构造函数调用之前就赋值了.
m_del的赋值是在smart_ptr的构造函数被调用时赋值的
3.make_smart的实现方式 与 好处
template <typename T, typename... Args>
smart_ptr<T> make_smart(Args&&... args)
{smart_ptr<T> sp(new T(std::forward<Args>(args)...));return sp;
}
这里右值引用和完美转发相关的知识在左值右值, 左值引用右值引用,完美转发这篇博客中有详细介绍.
这里和shared_ptr一样, 也是接收目标类的构造函数的参数, 直接返回智能指针的实现方式
好处: 我们使用make_smart的方式, 创建智能指针非常好, 因为不用我们手动new一个指针出来, 那么智能指针外部就没有该指针的变量, 就不会造成一些未知错误, 保证了在指针的生命周期内, 都是被智能指针安全管理的.
4. 几种构造函数
常用的构造函数和赋值运算符 在这篇文章里有介绍
C++基础知识,对象移动,拷贝构造函数,移动拷贝构造函数,赋值运算符,移动赋值运算符
4.1 空构造函数
template <typename T>
smart_ptr<T>::smart_ptr():m_pobject(nullptr), m_p_use_count(new unsigned(1))
{}
这个方法是构造一个内容为空的智能指针, 声明方式如下
smart_prt<MyClass> sp();
这个可以配合后面的reset方法使用, 给智能指针重新赋值
4.2 接收指针的构造函数
template <typename T>
smart_ptr<T>::smart_ptr(T *p):m_pobject(p), m_p_use_count(new unsigned(1))
{}
使用方式如下, 但是不推荐这种方式, 因为外部有了指针变量s1, 这就给智能指针的管理带来了未知的风险
MyStruct* s1 = new MyStruct(1, 2);
smart_ptr<MyStruct> sp(s1);
4.3 接收指针和删除方法的构造函数 , 以及auto进行模板lambda的编写
template <typename T>
smart_ptr<T>::smart_ptr(T *p, std::function<void(T*)> del):m_pobject(p), m_p_use_count(new unsigned(1)), m_del(del)
{}
这个构造方法就是上面4.2多加一个参数, 没什么好说的, 但是其中delete方法的传入, 我们可以通过auto来编写模板lambda, 这是c++新的特性, 如下
auto deleter = [](auto* p) {delete *p; p = nullptr;};
MyStruct* s = new MyStruct(1, 2);
smart_ptr<MyStruct> sp(s, deleter);
这样, 我们就不用template写一大堆, 而直接构造出了一个模板函数
4.4 拷贝构造函数
拷贝构造函数, 会造成引用计数+1, 两个智能指针指向的是同一块地址
template <typename T>
smart_ptr<T>::smart_ptr(const smart_ptr& rhs) :m_pobject(rhs.m_pobject), m_p_use_count(rhs.m_p_use_count), m_del(rhs.m_del)
{(*m_p_use_count)++;
}
=符号的赋值, 是赋值的内容, 不是变量本身的地址设置为一致(这是引用的=), 所以指针的=, 是指针变量内指向的地址设置为相同
MyStruct* s1 = new MyStruct(1, 2);
smart_ptr<MyStruct> sp(s1);
smart_ptr<MyStruct> sp2(sp);
cout << std::boolalpha;
cout << (sp.get() == sp2.get()) << endl;
执行结果

4.5 赋值运算符
赋值运算符 和 拷贝构造函数就大不一样了, 赋值运算符会把等号左边的智能指针内的变量引用计数-1, 如果引用计数为0了, 还会释放资源
template <typename T>
smart_ptr<T>& smart_ptr<T>::operator =(const smart_ptr &rhs)
{// 使用rhs的deleterm_del = rhs.m_del;// 递增右侧运算对象的引用计数++(*rhs.m_p_use_count);// 递减本对象的引用计数if (--(*m_p_use_count) == 0){// 如果管理的对象没有其他用户了,则释放对象分配的成员m_del(m_pobject);delete m_p_use_count;}m_p_use_count = rhs.m_p_use_count;m_pobject = rhs.m_pobject;return *this; // 返回本对象
}
所以根据代码, 我们合理预测, 如果=左边的智能指针引用计数如果是1的话, 那么使用了赋值运算符, 就会造成内部的指针析构
测试代码
struct MyStruct
{MyStruct() = default;MyStruct(int a, int b) :a(a), b(b) {}~MyStruct() { cout << "~MyStruct (" << a << "," << b << ")" << endl;}int a;int b;
};int main()
{smart_ptr<MyStruct> sp1 = make_smart<MyStruct>(1, 2);smart_ptr<MyStruct> sp2 = make_smart<MyStruct>(3, 4);sp1 = sp2;cout << "==========" << endl;return 0;
}
执行结果

可以看到, 是sp1在程序结束之前析构的
5. release函数, 指针的delete 和 设置为nullptr
这里我们要明确,delete是把指针指向的内容进行析构, 而直接把指针设置为nullptr, 只是把当前这个指针变量设置为nullptr, 对于指针指向的内容, 不做任何处理
比如release函数
template <typename T>
T* smart_ptr<T>::release()
{(*m_p_use_count)--;if (*m_p_use_count == 0){*m_p_use_count = 1;}auto p = m_pobject;m_pobject = nullptr;return p;
}
在这个函数里, 只是把m_pojbect这个指针变量本身设置为了nullptr, 而指针指向的变量没有做任何操作, 最后把这个指针变量copy一份, 返回出去了
测试代码
struct MyStruct
{MyStruct() = default;MyStruct(int a, int b) :a(a), b(b) {}~MyStruct() { cout << "~MyStruct (" << a << "," << b << ")" << endl;}int a;int b;
};int main()
{smart_ptr<MyStruct> sp1 = make_smart<MyStruct>(1, 2);MyStruct* s1 = sp1.release();cout << "=========" << endl;delete s1;return 0;
}
执行结果 可以看到, release确实没有造成析构

6. 获取内部变量, 指针和引用
有两个方法, 一个是重载的操作符*, 一个是get方法
只不过get获取的指针
*获取的是引用
template <typename T>
T& smart_ptr<T>::operator*() const
{return *m_pobject;
}
template <typename T>
T* smart_ptr<T>::get() const
{return m_pobject;
}
测试代码
int main()
{smart_ptr<MyStruct> sp1 = make_smart<MyStruct>(1, 2);cout << std::boolalpha << endl;// (*sp1)获取了引用: &MyStruct, 转为指针 *(&MyStruct)cout << (&(*sp1) == sp1.get()) << endl;return 0;
}
执行结果

相关文章:
c++源码阅读__smart_ptr__正文阅读
文章目录 简介源码解析1. 引用计数的实现方式2. deleter静态方法的赋值时间节点3.make_smart的实现方式 与 好处4. 几种构造函数4.1 空构造函数4.2 接收指针的构造函数4.3 接收指针和删除方法的构造函数 , 以及auto进行模板lambda的编写4.4 拷贝构造函数4.5 赋值运算符 5. rele…...
图形化界面MySQL(MySQL)(超级详细)
1.官网地址 MySQL :: Download MySQL Workbench 1.1在Linux直接点击NO thanks..... 下载完后是这个页面 1.2任何远端登录,再把jj数据库给授权 1.3建立新用户 进行连接 点击这个就运行了 只执行show tables;要先选中 圆圈处支持自己输入 点击这个就执…...
【2024 Optimal Control 16-745】Julia语法
Lecture 2 θ和它的导数符号是通过 Julia 中的变量命名方式实现的 变量 θ 的输入: 在 Julia 中,θ 是一个合法的变量名,就像普通的字母 x 或 y 一样。要输入 θ,可以使用以下方法: 在 Jupyter Notebook 或 Julia REP…...
Opencv+ROS实现摄像头读取处理画面信息
一、工具 ubuntu18.04 ROSopencv2 编译器:Visual Studio Code 二、原理 图像信息 ROS数据形式:sensor_msgs::Image OpenCV数据形式:cv:Mat 通过cv_bridge()函数进行ROS向opencv转换 cv_bridge是在ROS图像消息和OpenCV图像之间进行转…...
网络安全,文明上网(2)加强网络安全意识
前言 在当今这个数据驱动的时代,对网络安全保持高度警觉已经成为每个人的基本要求。 网络安全意识:信息时代的必备防御 网络已经成为我们生活中不可或缺的一部分,信息技术的快速进步使得我们对网络的依赖性日益增强。然而,网络安全…...
深度学习实战图像缺陷修复
这里写目录标题 概述1. 图像缺陷修复的研究背景2. 传统图像缺陷修复方法的局限性(1) 基于纹理合成的方法(2) 基于偏微分方程(PDE)的方法 3. 深度学习在图像缺陷修复中的兴起(1) 深度学习的基本思路(2) 深度学习方法的优势(3) 关键技术的引入 4. 深度学习…...
jenkins 2.346.1最后一个支持java8的版本搭建
1.jenkins下载 下载地址:Index of /war-stable/2.346.1 2.部署 创建目标文件夹,移动到指定位置 创建一个启动脚本,deploy.sh #!/bin/bash set -eDATE$(date %Y%m%d%H%M) # 基础路径 BASE_PATH/opt/projects/jenkins # 服务名称。同时约定部…...
【数据库原理】创建与维护表,DDL数据定义语言
数据描述语言(数据定义语言) 就是管理数据库整个库,整个表,表的属性列的语句。 常用词儿就是数据库或表的增删改查:CREATE创建、DROP删除、ALTER修改、SHOW查看、USE进入表。 表的字段控制:PRIMARY KEY主键…...
驾驭Go语言中的不确定性:深入错误处理机制
驾驭Go语言中的不确定性:深入错误处理机制 在Go语言的编程世界中,错误处理是确保程序健壮性的关键。Go语言通过显式的错误返回值和panic/recover机制,提供了一套独特的错误处理策略。本文将深入探讨Go语言中的错误处理,包括原理、技术细节和实际案例,帮助读者在实际编程中…...
3D Gaussian Splatting在鱼眼相机中的应用与投影变换
paper:Fisheye-GS 1.概述 3D 高斯泼溅 (3DGS) 因其高保真度和实时渲染而备受关注。然而,由于独特的 3D 到 2D 投影计算,将 3DGS 适配到不同的相机型号(尤其是鱼眼镜头)带来了挑战。此外,基于图块的泼溅效率低下,尤其是对于鱼眼镜头的极端曲率和宽视野,这对于其更广泛…...
【Unity踩坑】在Mac上安装Cocoapods失败
在集成Unity Ad时,如果是第一次在iOS上集成,会在Mac上安装Cocoapods。 安装时提示下面的错误: Error installing cocoapods:The last version of drb (> 0) to support your Ruby & RubyGems was 2.0.5. Try installing it with gem…...
uni-app 认识条件编译,了解多端部署
一. 前言 在使用 uni-app 进行跨平台开发的过程中,经常会遇到需要针对不同平台或不同环境进行条件编译的情况。条件编译是一种在编译过程中根据指定条件选择不同代码路径的技术,可以帮助我们在不同平台或环境下编写不同的代码,以适应不同的平…...
SPA 首屏加载慢的原因及解决方案:结合实际项目的详细讲解
在现代前端开发中,单页面应用程序 (SPA) 的首屏加载速度是用户体验的关键因素之一。首屏加载慢会直接影响用户对网站的第一印象,甚至导致用户流失。因此,优化首屏加载速度是每个前端开发者需要重点关注的内容。 1. 什么是首屏加载? 首屏加载指的是用户访问一个网站或应用…...
vue3+ts el-tabel 搜索组件
爷爷页面 <template> <searchstyle"z-index: 9999":options"options"placeholder"请选择时间,或输入名称、单选、多个勾选、模糊查询"search"onSearch"></search> </template> <script lan…...
leetcode 排序算法汇总
快速排序 def quicksort(arr): if len(arr) < 1: return arr else: pivot arr[len(arr) // 2] # 选择中间值作为基准 left [x for x in arr if x < pivot] # 小于基准的放左边 middle [x for x in arr if x pivot] # 等…...
【C】错误的变量定义导致sprintf()输出错误
问题描述 刚刚写一个用AT指令透传相关的函数,需要用到sprintf()拼接字符串。 结果发现sprintf()拼接出来的内容是错误的,简化后的代码如下: const char AT_CIPSEND_FIX_LENGTH_HEADER[11] "ATCIPSEND"; // 错误的࿰…...
python基础导包
Python项目代码结构与导包详解 目录 引言 Python项目的基本结构 2.1 单文件项目2.2 多模块项目2.3 包结构项目2.4 示例项目结构 模块与包 3.1 模块(Module)3.2 包(Package)3.3 子包(Subpackage) 导包&a…...
【含开题报告+文档+PPT+源码】基于SSM的电影数据挖掘与分析可视化系统设计与实现
开题报告 随着互联网的普及和数字娱乐产业的蓬勃发展,电影作为一种重要的娱乐方式,已经深入人们的日常生活。然而,面对海量的电影资源,用户在选择观影内容时常常感到困惑和无所适从。传统的电影推荐方式,如人工筛选、…...
strlwr(arr);的模拟实现(c基础)
hi , I am 36 适合对象c语言初学者 strlwr(arr);函数是把arr数组变为小写字母,并返回arr 链接介绍一下strlwr(arr);(c基础)-CSDN博客 下面进行My__strlwr(arr);模拟实现 #include<stdio.h> //返回值为arr(地址),于是用指针变量,原数组为字符型…...
LCR 002. 二进制求和
一.题目: . - 力扣(LeetCode) 二.原始解法-利用二进制逢二进一: 自己实现的时候忽略了一点,就是进位是会滚动的,不是进位一次就结束,很复杂跳过 三.正确解法及好的讲解、力扣解法参考…...
Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误
HTTP 状态码 406 (Not Acceptable) 和 500 (Internal Server Error) 是两类完全不同的错误,它们的含义、原因和解决方法都有显著区别。以下是详细对比: 1. HTTP 406 (Not Acceptable) 含义: 客户端请求的内容类型与服务器支持的内容类型不匹…...
Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...
Java多线程实现之Callable接口深度解析
Java多线程实现之Callable接口深度解析 一、Callable接口概述1.1 接口定义1.2 与Runnable接口的对比1.3 Future接口与FutureTask类 二、Callable接口的基本使用方法2.1 传统方式实现Callable接口2.2 使用Lambda表达式简化Callable实现2.3 使用FutureTask类执行Callable任务 三、…...
React19源码系列之 事件插件系统
事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...
相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...
什么是Ansible Jinja2
理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具,可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板,允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板,并通…...
基于 TAPD 进行项目管理
起因 自己写了个小工具,仓库用的Github。之前在用markdown进行需求管理,现在随着功能的增加,感觉有点难以管理了,所以用TAPD这个工具进行需求、Bug管理。 操作流程 注册 TAPD,需要提供一个企业名新建一个项目&#…...
Python基于历史模拟方法实现投资组合风险管理的VaR与ES模型项目实战
说明:这是一个机器学习实战项目(附带数据代码文档),如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 在金融市场日益复杂和波动加剧的背景下,风险管理成为金融机构和个人投资者关注的核心议题之一。VaR&…...
基于IDIG-GAN的小样本电机轴承故障诊断
目录 🔍 核心问题 一、IDIG-GAN模型原理 1. 整体架构 2. 核心创新点 (1) 梯度归一化(Gradient Normalization) (2) 判别器梯度间隙正则化(Discriminator Gradient Gap Regularization) (3) 自注意力机制(Self-Attention) 3. 完整损失函数 二…...
SQL Server 触发器调用存储过程实现发送 HTTP 请求
文章目录 需求分析解决第 1 步:前置条件,启用 OLE 自动化方式 1:使用 SQL 实现启用 OLE 自动化方式 2:Sql Server 2005启动OLE自动化方式 3:Sql Server 2008启动OLE自动化第 2 步:创建存储过程第 3 步:创建触发器扩展 - 如何调试?第 1 步:登录 SQL Server 2008第 2 步…...
