当前位置: 首页 > news >正文

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任何远端登录&#xff0c;再把jj数据库给授权 1.3建立新用户 进行连接 点击这个就运行了 只执行show tables&#xff1b;要先选中 圆圈处支持自己输入 点击这个就执…...

【2024 Optimal Control 16-745】Julia语法

Lecture 2 θ和它的导数符号是通过 Julia 中的变量命名方式实现的 变量 θ 的输入&#xff1a; 在 Julia 中&#xff0c;θ 是一个合法的变量名&#xff0c;就像普通的字母 x 或 y 一样。要输入 θ&#xff0c;可以使用以下方法&#xff1a; 在 Jupyter Notebook 或 Julia REP…...

Opencv+ROS实现摄像头读取处理画面信息

一、工具 ubuntu18.04 ROSopencv2 编译器&#xff1a;Visual Studio Code 二、原理 图像信息 ROS数据形式&#xff1a;sensor_msgs::Image OpenCV数据形式&#xff1a;cv:Mat 通过cv_bridge()函数进行ROS向opencv转换 cv_bridge是在ROS图像消息和OpenCV图像之间进行转…...

网络安全,文明上网(2)加强网络安全意识

前言 在当今这个数据驱动的时代&#xff0c;对网络安全保持高度警觉已经成为每个人的基本要求。 网络安全意识&#xff1a;信息时代的必备防御 网络已经成为我们生活中不可或缺的一部分&#xff0c;信息技术的快速进步使得我们对网络的依赖性日益增强。然而&#xff0c;网络安全…...

深度学习实战图像缺陷修复

这里写目录标题 概述1. 图像缺陷修复的研究背景2. 传统图像缺陷修复方法的局限性(1) 基于纹理合成的方法(2) 基于偏微分方程&#xff08;PDE&#xff09;的方法 3. 深度学习在图像缺陷修复中的兴起(1) 深度学习的基本思路(2) 深度学习方法的优势(3) 关键技术的引入 4. 深度学习…...

jenkins 2.346.1最后一个支持java8的版本搭建

1.jenkins下载 下载地址&#xff1a;Index of /war-stable/2.346.1 2.部署 创建目标文件夹&#xff0c;移动到指定位置 创建一个启动脚本&#xff0c;deploy.sh #!/bin/bash set -eDATE$(date %Y%m%d%H%M) # 基础路径 BASE_PATH/opt/projects/jenkins # 服务名称。同时约定部…...

【数据库原理】创建与维护表,DDL数据定义语言

数据描述语言&#xff08;数据定义语言&#xff09; 就是管理数据库整个库&#xff0c;整个表&#xff0c;表的属性列的语句。 常用词儿就是数据库或表的增删改查&#xff1a;CREATE创建、DROP删除、ALTER修改、SHOW查看、USE进入表。 表的字段控制&#xff1a;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时&#xff0c;如果是第一次在iOS上集成&#xff0c;会在Mac上安装Cocoapods。 安装时提示下面的错误&#xff1a; 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 进行跨平台开发的过程中&#xff0c;经常会遇到需要针对不同平台或不同环境进行条件编译的情况。条件编译是一种在编译过程中根据指定条件选择不同代码路径的技术&#xff0c;可以帮助我们在不同平台或环境下编写不同的代码&#xff0c;以适应不同的平…...

SPA 首屏加载慢的原因及解决方案:结合实际项目的详细讲解

在现代前端开发中,单页面应用程序 (SPA) 的首屏加载速度是用户体验的关键因素之一。首屏加载慢会直接影响用户对网站的第一印象,甚至导致用户流失。因此,优化首屏加载速度是每个前端开发者需要重点关注的内容。 1. 什么是首屏加载? 首屏加载指的是用户访问一个网站或应用…...

vue3+ts el-tabel 搜索组件

爷爷页面 <template> <searchstyle"z-index: 9999":options"options"placeholder"请选择时间&#xff0c;或输入名称、单选、多个勾选、模糊查询"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指令透传相关的函数&#xff0c;需要用到sprintf()‌拼接字符串。 结果发现sprintf()‌拼接出来的内容是错误的&#xff0c;简化后的代码如下&#xff1a; const char AT_CIPSEND_FIX_LENGTH_HEADER[11] "ATCIPSEND"; // 错误的&#xff0…...

python基础导包

Python项目代码结构与导包详解 目录 引言 Python项目的基本结构 2.1 单文件项目2.2 多模块项目2.3 包结构项目2.4 示例项目结构 模块与包 3.1 模块&#xff08;Module&#xff09;3.2 包&#xff08;Package&#xff09;3.3 子包&#xff08;Subpackage&#xff09; 导包&a…...

【含开题报告+文档+PPT+源码】基于SSM的电影数据挖掘与分析可视化系统设计与实现

开题报告 随着互联网的普及和数字娱乐产业的蓬勃发展&#xff0c;电影作为一种重要的娱乐方式&#xff0c;已经深入人们的日常生活。然而&#xff0c;面对海量的电影资源&#xff0c;用户在选择观影内容时常常感到困惑和无所适从。传统的电影推荐方式&#xff0c;如人工筛选、…...

strlwr(arr);的模拟实现(c基础)

hi , I am 36 适合对象c语言初学者 strlwr(arr)&#xff1b;函数是把arr数组变为小写字母,并返回arr 链接介绍一下strlwr(arr)&#xff1b;(c基础)-CSDN博客 下面进行My__strlwr(arr);模拟实现 #include<stdio.h> //返回值为arr(地址),于是用指针变量,原数组为字符型…...

LCR 002. 二进制求和

一.题目&#xff1a; . - 力扣&#xff08;LeetCode&#xff09; 二.原始解法-利用二进制逢二进一&#xff1a; 自己实现的时候忽略了一点&#xff0c;就是进位是会滚动的&#xff0c;不是进位一次就结束&#xff0c;很复杂跳过 三.正确解法及好的讲解、力扣解法参考&#xf…...

docker详细操作--未完待续

docker介绍 docker官网: Docker&#xff1a;加速容器应用程序开发 harbor官网&#xff1a;Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台&#xff0c;用于将应用程序及其依赖项&#xff08;如库、运行时环…...

Prompt Tuning、P-Tuning、Prefix Tuning的区别

一、Prompt Tuning、P-Tuning、Prefix Tuning的区别 1. Prompt Tuning(提示调优) 核心思想:固定预训练模型参数,仅学习额外的连续提示向量(通常是嵌入层的一部分)。实现方式:在输入文本前添加可训练的连续向量(软提示),模型只更新这些提示参数。优势:参数量少(仅提…...

前端导出带有合并单元格的列表

// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...

在四层代理中还原真实客户端ngx_stream_realip_module

一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡&#xff08;如 HAProxy、AWS NLB、阿里 SLB&#xff09;发起上游连接时&#xff0c;将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后&#xff0c;ngx_stream_realip_module 从中提取原始信息…...

Module Federation 和 Native Federation 的比较

前言 Module Federation 是 Webpack 5 引入的微前端架构方案&#xff0c;允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...

《基于Apache Flink的流处理》笔记

思维导图 1-3 章 4-7章 8-11 章 参考资料 源码&#xff1a; https://github.com/streaming-with-flink 博客 https://flink.apache.org/bloghttps://www.ververica.com/blog 聚会及会议 https://flink-forward.orghttps://www.meetup.com/topics/apache-flink https://n…...

学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2

每日一言 今天的每一份坚持&#xff0c;都是在为未来积攒底气。 案例&#xff1a;OLED显示一个A 这边观察到一个点&#xff0c;怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 &#xff1a; 如果代码里信号切换太快&#xff08;比如 SDA 刚变&#xff0c;SCL 立刻变&#…...

C# 求圆面积的程序(Program to find area of a circle)

给定半径r&#xff0c;求圆的面积。圆的面积应精确到小数点后5位。 例子&#xff1a; 输入&#xff1a;r 5 输出&#xff1a;78.53982 解释&#xff1a;由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982&#xff0c;因为我们只保留小数点后 5 位数字。 输…...

推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材)

推荐 github 项目:GeminiImageApp(图片生成方向&#xff0c;可以做一定的素材) 这个项目能干嘛? 使用 gemini 2.0 的 api 和 google 其他的 api 来做衍生处理 简化和优化了文生图和图生图的行为(我的最主要) 并且有一些目标检测和切割(我用不到) 视频和 imagefx 因为没 a…...

深度剖析 DeepSeek 开源模型部署与应用:策略、权衡与未来走向

在人工智能技术呈指数级发展的当下&#xff0c;大模型已然成为推动各行业变革的核心驱动力。DeepSeek 开源模型以其卓越的性能和灵活的开源特性&#xff0c;吸引了众多企业与开发者的目光。如何高效且合理地部署与运用 DeepSeek 模型&#xff0c;成为释放其巨大潜力的关键所在&…...