C++ 实现智能指针:shared_ptr 和 unique_ptr

news/2025/1/15 16:37:53/

简 述: C++11 智能指针的深入分析,和动手实现简版的智能指针 std::shared_ptrstd::unique_ptr

文章目录

    • 背景
    • std::shared_ptr
      • 原理
      • 代码
      • reference
    • std::unique_ptr
      • 原理
      • 代码
      • reference
    • 系列


本文初发于 “偕臧的小站”,同步转载于此。


背景

实现原理提前需要理解 特殊成员函数std::exchange() C++14std::swap()std::move()constexprexplicitnoexcept 等,若是遗忘可参考此文

  • C++ 类的六个特殊成员函数
  • C++ 11/14/17 的新特性 占位

最后,Demo 实现或许不够十分完美和严谨,但对于其理解智能指针的原理和面试手写实现时候,足够。若有纰漏,请指正。


std::shared_ptr

原理

  • shared_ptr 的原理: 通过引用计数的方式来实现多个 shared_ptr 对象之间共享资源。

  • 通过引用计数和模板来实现 shared_ptr;构造函数定义的时候,要初始化其指针、引用计数、和 mutex

  • “copy assignment constructor” 除了校验是否相等、是否为空的时候、拷贝时要先释放旧资源,旧的引用计数 -1,赋值后再指向对新的资源的引用计数 +1

  • 释放资源时,要先校验是否存在,及计数为 0 才释放;


代码

💻 win10 22H2 📎 Visual Studio 2019 📎 C++11 见 SharedPtr.h

/******************************************************************** Copyright (c) 2022~-023 XMuli  All rights reserved.* Description: C++ 实现一个核心的 shared_ptr 智能指针模板类******************************************************************/
#pragma once
#include <iostream>
#include <mutex>
using namespace std;template <typename T>
class SharedPtr
{
public:SharedPtr() : _ptr(nullptr), _refCount(nullptr), _pMutex(nullptr) { cout << "default constructor" << endl; };SharedPtr(T* obj) : _ptr(obj), _refCount(new int(1)), _pMutex(new mutex) { cout << "no default constructor" << endl; };SharedPtr(const SharedPtr<T>& obj)  // 其 _refCount 可以通过另外一个指针来修改,指向的是同一个地址: _ptr(obj._ptr), _refCount(obj._refCount), _pMutex(obj._pMutex){cout << "copy constructor" << endl;addRefCount();};SharedPtr<T>& operator=(const SharedPtr<T>& obj){cout << "copy assignment constructor" << endl;if (&obj != this) {if (_ptr != obj._ptr) {release();            // 先释放旧的资源_ptr = obj._ptr;_refCount = obj._refCount;_pMutex = obj._pMutex;addRefCount();        // 再技计数 +1}}return *this;}//SharedPtr(SharedPtr<T>&& obj) noexcept;//SharedPtr<T>& operator=(SharedPtr<T>&& obj)noexcept;~SharedPtr() { cout << "destructor" << endl; release(); }T& operator*() { return *_ptr; }T* operator->() { return _ptr; }int useCount() { return *_refCount; }T* get() { return _ptr; }private:void addRefCount(){cout << "addRefCount" << endl;_pMutex->lock();++*_refCount;_pMutex->unlock();}void release(){cout << "release" << endl;bool bDelMutex = false;_pMutex->lock();if (_ptr && --*_refCount == 0) {  // 先校验是否存在,及计数为 0 才释放delete _ptr;delete _refCount;_ptr = nullptr;_refCount = nullptr;bDelMutex = true;}_pMutex->unlock();if (bDelMutex)delete _pMutex;}private:                  // 需在构造函数中初始化T* _ptr;              // 指向管理资源的指针int* _refCount;       // 引用计数mutex* _pMutex;       // 计数自增非原子操作,加锁解决多线程
};int main()
{SharedPtr<int> sp1(new int(10));SharedPtr<int> sp2(sp1);*sp2 = 20;//sp1 与 sp2 在管理这部分资源,引用计数为 2cout << sp1.useCount() << "  *ptr:" << *sp1 << endl;		//2	 20cout << sp2.useCount() << "  *ptr:" << *sp2 << endl;		//2	 20SharedPtr<int> sp3(new int(30));                              sp2 = sp3;		                                            //sp3 赋值给它,释放管理的旧资源,引用计数-1,   cout << sp1.useCount() << "  *ptr:" << *sp1 << endl;        //1	 20cout << sp2.useCount() << "  *ptr:" << *sp2 << endl;        //2	 30cout << sp3.useCount() << "  *ptr:" << *sp3 << endl;        //2	 30sp1 = sp3;                                                    cout << sp1.useCount() << "  *ptr:" << *sp1 << endl;        //3	 30cout << sp2.useCount() << "  *ptr:" << *sp2 << endl;        //3	 30cout << sp3.useCount() << "  *ptr:" << *sp3 << endl;        //3	 30std::cout << "Hello World!\n";return 0;
}/*****************************打印结果*******************************
no default constructor
copy constructor
addRefCount
2  *ptr:20
2  *ptr:20
no default constructor
copy assignment constructor
release
addRefCount
1  *ptr:20
2  *ptr:30
2  *ptr:30
copy assignment constructor
release
addRefCount
3  *ptr:30
3  *ptr:30
3  *ptr:30
Hello World!
destructor
release
destructor
release
destructor
release******************************************************************/

Note:

  • mutex 实现了引用计数是线程安全的。但智能指针管理的对象存放在上,两个线程中同时去访问,会导致线程安全问题。

  • 书写测试时,若使用默认构造函数, 成员变量 _ptr、_refCount、_pMutex 在 release() 中容易崩溃;推荐带参的构造函数,完美运行测试


reference

  • C++ 智能指针与底层实现剖析
  • 面试题:简单实现一个shared_ptr智能指针
  • C++智能指针: shared_ptr 实现详解

std::unique_ptr

原理

  • unique_ptr的设计思路非常的粗暴:防拷贝,也就是不让拷贝和赋值
  • unique_ptr 唯一 拥有其所指对象,同一时刻只能有一个unique_ptr 指向给定对象(通过禁止拷贝语义、只有移动语义来实现

代码

💻 win10 22H2 📎 Visual Studio 2019 📎 C++11 见 UniquePtr.h

/******************************************************************** Copyright (c) 2022~2023 XMuli  All rights reserved.* Description: C++ 实现一个核心的 unique_ptr 智能指针模板类;******************************************************************/
#pragma once
#include <iostream>
using namespace std;template <typename T>
class UniquePtr
{
public:constexpr UniquePtr() : _ptr(nullptr) { cout << "default constructor" << endl; };explicit  UniquePtr(T* obj) : _ptr(obj) { cout << "no default constructor" << endl; };UniquePtr(UniquePtr<T>&& obj) noexcept : _ptr(obj._ptr) {cout << "move constructor" << endl;obj._ptr = nullptr;}UniquePtr<T>& operator=(UniquePtr<T>&& obj) noexcept{cout << "move assignment constructor" << endl;if (&obj != this) {if (obj._ptr) {_ptr = obj._ptr;obj._ptr = nullptr;}}return *this;}UniquePtr(const UniquePtr<T>& obj) = delete;		// C++11 delete 禁止方式,C++98 用 private 来隐藏UniquePtr<T>& operator=(const UniquePtr<T>& obj) = delete;~UniquePtr(){cout << "destructor" << endl;if (_ptr) {delete _ptr;_ptr = nullptr;}}T& operator*() const { return *_ptr; }T* operator->() const { return _ptr; }T* get() const { return _ptr; }T* release()            // return std::exchange(_ptr, nullptr); // C++14{T* temp = _ptr;_ptr = nullptr;return temp;}void reset(T* ptr)		// std::exchange(_ptr, ptr); // C++14{_ptr = ptr;}void swap(UniquePtr<T>& obj){std::swap(_ptr, obj._ptr);}private:T* _ptr;
};int main()
{UniquePtr<int> up1(new int(10));cout << "up1:" << up1.get() << "  *ptr:" << *up1 << endl;UniquePtr<int> up2(std::move(up1));                 // 控制权变更cout << "up1:" << up1.get() << endl;		        // nullptr, 此时 up1 已无控制权cout << "up2:" << up2.get() << "  *ptr:" << *up2 << endl;UniquePtr<int> up3(new int(30));UniquePtr<int> up4(new int(40));cout << "up3:" << up3.get() << "  *ptr:" << *up3 << endl;cout << "up4:" << up4.get() << "  *ptr:" << *up4 << endl;up3 = std::move(up2);                               // 控制权变更cout << "up3:" << up3.get() << "  *ptr:" << *up3 << endl;cout << "up4:" << up4.get() << "  *ptr:" << *up4 << endl;up3.swap(up4);cout << "up3:" << up3.get() << "  *ptr:" << *up3 << endl;cout << "up4:" << up4.get() << "  *ptr:" << *up4 << endl;up3.release();cout << "up3:" << up3.get() << endl;std::cout << "Hello World!\n";return 0;
}/*****************************打印结果*******************************
no default constructor
up1:0086DEB8  *ptr:10
move constructor
up1:00000000
up2:0086DEB8  *ptr:10
no default constructor
no default constructor
up3:008656D0  *ptr:30
up4:00865700  *ptr:40
move assignment constructor
up3:0086DEB8  *ptr:10
up4:00865700  *ptr:40
up3:00865700  *ptr:40
up4:0086DEB8  *ptr:10
up3:00000000
Hello World!
destructor
destructor
destructor
destructor******************************************************************/

reference

  • 二、C++实现unique_ptr
  • 面试官的动机——实现智能指针1:unique_ptr
  • C++进阶:智能指针之unique_ptr

系列

QtExamples 【Studio】

欢迎 star ⭐ 和 fork 🍴这个系列的 C++ / QT / DTK 学习,附学习由浅入深的目录。


http://www.ppmy.cn/news/6456.html

相关文章

SpringBoot图文详解 | 系统性学习 | 无知的我费曼笔记

无知的我复盘&#xff0c;顺便上传笔记。 对比Spring&#xff0c;SpringBoot 其实就是简化了固定的开发步骤。如坐标、Web3.0配置类、配置类 文章目录1 SpringBoot简介1.1 SpringBoot快速入门1.1.1 开发步骤1.1.1.1 创建新模块1.1.1.2 创建 Controller1.1.1.3 启动服务器1.1.1.…

【实时数仓】DWS层之地区主题表(FlinkSQL)、关键词主题表(FlinkSQL)

文章目录一 DWS层-地区主题表(FlinkSQL)1 分组开窗和聚合计算&#xff08;1&#xff09;分组窗口&#xff08;2&#xff09;选择分组窗口的开始和结束时间戳&#xff08;3&#xff09;系统内置函数&#xff08;4&#xff09;完整代码2 将动态表转换为流&#xff0c;写入ClickHo…

现在请你帮忙编程计算出每位学生的总分和每门课程的平均分,以便帮助他快速统计出同学们的课程学习效果吧

【题目描述】 大一第一学期的期中考试结束了&#xff0c;辅导员急切的想了解同学们的学习情况&#xff0c;现在请你帮忙编程计算出每位学生的总分和每门课程的平均分&#xff0c;以便帮助他快速统计出同学们的课程学习效果吧&#xff01; 【输入】 有多行。第1行是两个整数n…

中文文本分类

手把手带你做一个文本分类实战项目(模型代码解读) https://www.bilibili.com/video/BV15Z4y1S7aR/?spm_id_from333.788.recommend_more_video.-1&vd_sourcec47fbb8166930edc486d8fdc405bf569 中文汉字对应的数字索引 之后对应的数字索引 之后找到tokn embedding的东西 1…

暂时性死区以及函数作用域

暂时性死区 暂时性死区也就是变量声明到声明完成的区块&#xff0c;这个区块是一个封闭的作用域&#xff0c;直到声明完成。 如果在变量声明之前使用该变量&#xff0c;那么该变量是不可用的&#xff0c;也就被称为暂时性死区。 var 没有暂时性死区&#xff0c;因为var存在变…

12月24日:数据结构

Btree结构 ​​​​​​ BTree和BTree详解_菜鸟笔记的博客-CSDN博客_btree 简单的说一下什么是聚簇索引 , 和非聚簇索引有啥区别 聚簇索引&#xff1a;索引和数据存储放在了同一个文件中&#xff0c;找到了索引也就能找到数据 非聚簇索引&#xff1a;将数据存储和索引分开放置…

使用 Swift/SwiftUI 的音频可视化

IOS 应用程序中的音频可视化是一项流行的功能,在实现聊天功能时可能需要它。将它添加到我最近的项目之一时,我个人遇到了一些问题。 你们中的一些人可能在开发包含聊天功能的应用程序时遇到过问题,其中某些消息可能包含音频。这些消息需要在应用程序内进行适当的音频可视化,…

(倒序输出数组)交换的规则是:1号礼物与n号礼物互换,2号礼物与n-1号礼物互换,以此类推,请帮助他们找到喜欢的礼物吧!

【题目描述】 一年一度的毕业季&#xff0c;马上要离开校园踏上工作岗位的同学们在拍摄完照片之后&#xff0c;大家有了一个共同的想法——那就是在毕业前互换礼物。到了约定的日子&#xff0c;同学们带来的礼物琳琅满目&#xff0c;都不知道该如何交换了。于是&#xff0c;在…