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

文章目录

    • 简介
    • 源码
    • 解析
      • [1. 引用计数的实现方式](#1. 引用计数的实现方式)
      • [2. deleter静态方法的赋值时间节点](#2. deleter静态方法的赋值时间节点)
      • [3.make_smart的实现方式 与 好处](#3.make_smart的实现方式 与 好处)
      • [4. 几种构造函数](#4. 几种构造函数)
        • [4.1 空构造函数](#4.1 空构造函数)
        • [4.2 接收指针的构造函数](#4.2 接收指针的构造函数)
        • [4.3 接收指针和删除方法的构造函数 , 以及auto进行模板lambda的编写](#4.3 接收指针和删除方法的构造函数 , 以及auto进行模板lambda的编写)
        • [4.4 拷贝构造函数](#4.4 拷贝构造函数)
        • [4.5 赋值运算符](#4.5 赋值运算符)
      • [5. release函数, 指针的delete 和 设置为nullptr](#5. release函数, 指针的delete 和 设置为nullptr)
      • [6. 获取内部变量, 指针和引用](#6. 获取内部变量, 指针和引用)

简介

  • git地址: shimachao/smart_ptr
  • 说明: 这是一个仿写shared_ptr的库, 只有简短的200行, 实现了shared_ptr的大部分功能, 上手简单, 非常适合新手阅读.
  • 本文说明: 由于本项目没有那么多难点, 就不再像上篇开源代码阅读c++源码阅读__ThreadPool__正文阅读一样, 不单独开一个前提知识部分了, 由于本项目比较简单, 大家就当作对shared_ptr的一次复习. 看一乐呵就行.

源码

此代码在c++20环境下直接可以运行, 源码如下

cpp 复制代码
#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. 引用计数的实现方式

我们可以看到他的实现方式

cpp 复制代码
unsigned* m_p_use_count = nullptr;

使用的是整数的指针, 如果不使用指针的话, 那么是打不到公用引用计数的效果的, 如下

cpp 复制代码
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静态方法的赋值时间节点

cpp 复制代码
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的实现方式 与 好处

cpp 复制代码
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 空构造函数
cpp 复制代码
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 接收指针的构造函数
cpp 复制代码
template <typename T>
smart_ptr<T>::smart_ptr(T *p)
	:m_pobject(p), m_p_use_count(new unsigned(1))
{}

使用方式如下, 但是不推荐这种方式, 因为外部有了指针变量s1, 这就给智能指针的管理带来了未知的风险

cpp 复制代码
MyStruct* s1 = new MyStruct(1, 2);
smart_ptr<MyStruct> sp(s1);
4.3 接收指针和删除方法的构造函数 , 以及auto进行模板lambda的编写
cpp 复制代码
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++新的特性, 如下

cpp 复制代码
auto deleter = [](auto* p) {delete *p; p = nullptr;};
MyStruct* s = new MyStruct(1, 2);
smart_ptr<MyStruct> sp(s, deleter);

这样, 我们就不用template写一大堆, 而直接构造出了一个模板函数

4.4 拷贝构造函数

拷贝构造函数, 会造成引用计数+1, 两个智能指针指向的是同一块地址

cpp 复制代码
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)++;
}

=符号的赋值, 是赋值的内容, 不是变量本身的地址设置为一致(这是引用的=), 所以指针的=, 是指针变量内指向的地址设置为相同

cpp 复制代码
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了, 还会释放资源

cpp 复制代码
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的话, 那么使用了赋值运算符, 就会造成内部的指针析构
测试代码

cpp 复制代码
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函数

cpp 复制代码
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一份, 返回出去了
测试代码

cpp 复制代码
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获取的指针
*获取的是引用

cpp 复制代码
template <typename T>
T& smart_ptr<T>::operator*() const
{
	return *m_pobject;
}
template <typename T>
T* smart_ptr<T>::get() const
{
	return m_pobject;
}

测试代码

cpp 复制代码
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;
}

执行结果

相关推荐
绵绵细雨中的乡音44 分钟前
C++第28课-布隆过滤器的介绍
c++·哈希算法
雯0609~1 小时前
c#:winform调用bartender实现打印(学习整理笔记)
开发语言·c#
C++忠实粉丝1 小时前
计算机网络socket编程(5)_TCP网络编程实现echo_server
网络·c++·网络协议·tcp/ip·计算机网络·算法
程序猿阿伟2 小时前
《C++智能合约与区块链底层交互全解析:构建坚实的去中心化应用桥梁》
c++·区块链·智能合约
沐泽Mu2 小时前
嵌入式学习-C嘎嘎-Day08
开发语言·c++·算法
Non importa2 小时前
汉诺塔(hanio)--C语言函数递归
c语言·开发语言·算法·学习方法
LinuxST2 小时前
27、基于Firefly-rk3399中断休眠唤醒实验(按键中断)
linux·开发语言·stm32·嵌入式硬件
Tony_long74833 小时前
Python学习——猜拳小游戏
开发语言·python·学习
跳动的梦想家h3 小时前
黑马点评 秒杀下单出现的问题:服务器异常---java.lang.NullPointerException: null(已解决)
java·开发语言·redis