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) 二.原始解法-利用二进制逢二进一: 自己实现的时候忽略了一点,就是进位是会滚动的,不是进位一次就结束,很复杂跳过 三.正确解法及好的讲解、力扣解法参考…...
设计模式和设计原则回顾
设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...
定时器任务——若依源码分析
分析util包下面的工具类schedule utils: ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类,封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz,先构建任务的 JobD…...
Java多线程实现之Thread类深度解析
Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...
10-Oracle 23 ai Vector Search 概述和参数
一、Oracle AI Vector Search 概述 企业和个人都在尝试各种AI,使用客户端或是内部自己搭建集成大模型的终端,加速与大型语言模型(LLM)的结合,同时使用检索增强生成(Retrieval Augmented Generation &#…...
短视频矩阵系统文案创作功能开发实践,定制化开发
在短视频行业迅猛发展的当下,企业和个人创作者为了扩大影响力、提升传播效果,纷纷采用短视频矩阵运营策略,同时管理多个平台、多个账号的内容发布。然而,频繁的文案创作需求让运营者疲于应对,如何高效产出高质量文案成…...
动态 Web 开发技术入门篇
一、HTTP 协议核心 1.1 HTTP 基础 协议全称 :HyperText Transfer Protocol(超文本传输协议) 默认端口 :HTTP 使用 80 端口,HTTPS 使用 443 端口。 请求方法 : GET :用于获取资源,…...
如何更改默认 Crontab 编辑器 ?
在 Linux 领域中,crontab 是您可能经常遇到的一个术语。这个实用程序在类 unix 操作系统上可用,用于调度在预定义时间和间隔自动执行的任务。这对管理员和高级用户非常有益,允许他们自动执行各种系统任务。 编辑 Crontab 文件通常使用文本编…...
【Linux】Linux 系统默认的目录及作用说明
博主介绍:✌全网粉丝23W,CSDN博客专家、Java领域优质创作者,掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围:SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…...
Java求职者面试指南:Spring、Spring Boot、Spring MVC与MyBatis技术解析
Java求职者面试指南:Spring、Spring Boot、Spring MVC与MyBatis技术解析 一、第一轮基础概念问题 1. Spring框架的核心容器是什么?它的作用是什么? Spring框架的核心容器是IoC(控制反转)容器。它的主要作用是管理对…...
相关类相关的可视化图像总结
目录 一、散点图 二、气泡图 三、相关图 四、热力图 五、二维密度图 六、多模态二维密度图 七、雷达图 八、桑基图 九、总结 一、散点图 特点 通过点的位置展示两个连续变量之间的关系,可直观判断线性相关、非线性相关或无相关关系,点的分布密…...
