c++源码阅读__smart_ptr__正文阅读

news2024/11/24 12:35:19

文章目录

    • 简介
    • 源码
    • 解析
      • 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:
	// 默认的deleter
	static 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的deleter
	m_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:
	// 默认的deleter
	static 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的deleter
	m_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;
}

执行结果
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2246659.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【BUG】ES使用过程中问题解决汇总

安装elasticsearch内存不足问题 问题回顾 运行kibana服务的时候&#xff0c;无法本地访问 解决 首先排查端口问题&#xff0c;然后检查错误日志 无法运行kibana服务&#xff0c;是因为elasticsearch没有启动的原因 发现致命错误&#xff0c;确定是elasticsearch服务没有运行导…

C语言--分支循环编程题目

第一道题目&#xff1a; #include <stdio.h>int main() {//分析&#xff1a;//1.连续读取int a 0;int b 0;int c 0;while (scanf("%d %d %d\n", &a, &b, &c) ! EOF){//2.对三角形的判断//a b c 等边三角形 其中两个相等 等腰三角形 其余情…

Linux——用户级缓存区及模拟实现fopen、fweite、fclose

linux基础io重定向-CSDN博客 文章目录 目录 文章目录 什么是缓冲区 为什么要有缓冲区 二、编写自己的fopen、fwrite、fclose 1.引入函数 2、引入FILE 3.模拟封装 1、fopen 2、fwrite 3、fclose 4、fflush 总结 前言 用快递站讲述缓冲区 收件区&#xff08;类比输…

git(Linux)

1.git 三板斧 基本准备工作&#xff1a; 把远端仓库拉拉取到本地了 .git --> 本地仓库 git在提交的时候&#xff0c;只会提交变化的部分 就可以在当前目录下新增代码了 test.c 并没有被仓库管理起来 怎么添加&#xff1f; 1.1 git add test.c 也不算完全添加到仓库里面&…

GESP2023年9月认证C++四级( 第三部分编程题(1-2))

编程题1&#xff08;string&#xff09;参考程序&#xff1a; #include <iostream> using namespace std; long long hex10(string num,int b) {//int i;long long res0;for(i0;i<num.size();i) if(num[i]>0&&num[i]<9)resres*bnum[i]-0;else //如果nu…

Ultiverse 和web3新玩法?AI和GameFi的结合是怎样

Gamef 和 AI 是我们这个周期十分看好两大赛道之一&#xff0c;(Gamef 拥有极强的破圈效应&#xff0c;引领 Web2 用户进军 Web3 最佳利器。AI是这个周期最热门赛道&#xff0c;无论 Web2的 OpenAl&#xff0c;还是 Web3&#xff0c;都成为话题热议焦点。那么结合 GamefiA1双叙事…

如何在 UniApp 中实现 iOS 版本更新检测

随着移动应用的不断发展&#xff0c;保持应用程序的更新是必不可少的&#xff0c;这样用户才能获得更好的体验。本文将帮助你在 UniApp 中实现 iOS 版的版本更新检测和提示&#xff0c;适合刚入行的小白。我们将分步骤进行说明&#xff0c;每一步所需的代码及其解释都会一一列出…

解决 npm xxx was blocked, reason: xx bad guy, steal env and delete files

问题复现 今天一位朋友说&#xff0c;vue2的老项目安装不老依赖&#xff0c;报错内容如下&#xff1a; npm install 451 Unavailable For Legal Reasons - GET https://registry.npmmirror.com/vab-count - [UNAVAILABLE_FOR_LEGAL_REASONS] vab-count was blocked, reas…

【AI系统】GPU 架构回顾(从2018年-2024年)

Turing 架构 2018 年 Turing 图灵架构发布&#xff0c;采用 TSMC 12 nm 工艺&#xff0c;总共 18.6 亿个晶体管。在 PC 游戏、专业图形应用程序和深度学习推理方面&#xff0c;效率和性能都取得了重大进步。相比上一代 Volta 架构主要更新了 Tensor Core&#xff08;专门为执行…

每天五分钟机器学习:支持向量机数学基础之超平面分离定理

本文重点 超平面分离定理(Separating Hyperplane Theorem)是数学和机器学习领域中的一个重要概念,特别是在凸集理论和最优化理论中有着广泛的应用。该定理表明,在特定的条件下,两个不相交的凸集总可以用一个超平面进行分离。 定义与表述 超平面分离定理(Separating Hy…

docker镜像源配置、换源、dockerhub国内镜像最新可用加速源(仓库)

一、临时拉取方式 在docker pull后先拼接镜像源域名&#xff0c;后面拼接拉取的镜像名 $ docker pull dockerpull.org/continuumio/miniconda3 二、永久配置方式 vim修改/etc/docker/daemon.json&#xff0c;并重启docker服务。 # 创建目录 sudo mkdir -p /etc/docker# 写…

电脑使用——知乎、钉钉组件访问失败解决

最近发现办公电脑知乎、钉钉内置组件访问不了&#xff0c;但同网络下笔记本可以访问&#xff1b;经过检测排除了目标服务异常、防火墙拦截的原因&#xff1b;最后发现是DNS的原因&#xff0c;调整DNS首先项1.1.1.1为114.114.114.114后解决&#xff0c;现插眼记录 首先排除拦截&…

Consumer Group

不&#xff0c;kafka-consumer-groups.sh 脚本本身并不用于创建 Consumer Group。它主要用于管理和查看 Consumer Group 的状态和详情&#xff0c;比如列出所有的 Consumer Group、查看特定 Consumer Group 的详情、删除 Consumer Group 等。 Consumer Group 是由 Kafka 消费者…

pandas与open读取csv/txt文件速度比较

pandas与open读取csv/txt文件速度比较 由于在工作中经常需要读取txt或csv文件&#xff0c;使用pandas与open均可以读取并操作文件内容&#xff0c;但不知道那个速度更快一些&#xff0c;所以写了一个脚本去比较在文件大小不同的情况下读取数据的速度 测试结果: 大小pandas速度…

观察者模式和订阅模式

观察者模式和订阅模式在概念上是相似的&#xff0c;它们都涉及到一个对象&#xff08;通常称为“主题”或“发布者”&#xff09;和多个依赖对象&#xff08;称为“观察者”或“订阅者”&#xff09;之间的关系。然而&#xff0c;尽管它们有相似之处&#xff0c;但在某些方面也…

自主研发,基于PHP+ vue2+element+ laravel8+ mysql5.7+ vscode开发的不良事件管理系统源码,不良事件管理系统源码

不良事件上报系统源码&#xff0c;不良事件管理系统源码&#xff0c;PHP源码 不良事件上报系统通过 “事前的人员知识培训管理和制度落地促进”、“事中的事件上报和跟进处理”、 以及 “事后的原因分析和工作持续优化”&#xff0c;结合预存上百套已正在使用的模板&#xff0…

取电快充协议芯片,支持全协议、内部集成LDO支持从UART串口读取电压电流消息

H004D 是一款支持全协议的受电端诱骗取电协议芯片&#xff0c;支持宽电压输入 3.3V~30V&#xff0c;芯片内部集成LDO&#xff0c;可输出 3.3V电压, 支持 通过UART 串口读取电压电流&#xff0c;支持定制功能&#xff0c;芯片采用QFN_20封装&#xff0c;线路简单&#xff0c;芯片…

科研数据处理工具Graphpad Prism 10.1+9.5下载安装教程

GraphPad Prism 是一个功能强大且易于使用的统计分析和绘图软件&#xff0c;专为生物医学研究设计而开发。 该软件提供了丰富的统计工具和绘图功能&#xff0c;使用者能够轻松进行数据分析和统计检验。它支持一系列的统计方法&#xff0c;包括T检验、方差分析、非参数检验、生…

Flutter:AnimatedIcon图标动画,自定义Icon通过延时Interval,实现交错式动画

配置vsync&#xff0c;需要实现一下with SingleTickerProviderStateMixinclass _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin{// late延迟初始化 AnimationControllerlate AnimationController _controller;overridevoid initStat…

【网络系统管理】2023年全国职业院校技能大赛:组策略--10套题组合--1

1、限制访问C盘; (1)搜索《我的电脑》 (2)用户配置\策略\管理模板\Windows组件\文件资源管理器 2、禁止运行run.exe; (1)搜索《应用程序》 (2)用户配置\策略\管理模板\系统