C++:智能指针及其实现原理

2023-11-07

更多C++知识点:C++目录索引


1. RAII思想

  1. 定义一个类来封装资源的分配与释放,

  2. 构造函数中完成资源的分配及初始化;

  3. 析构函数中完成资源的清理,可以保证资源的正确初始化和释放

  4. 如果对象是用声明的方式在栈上创建局部对象,那么RAII机制就会正常工作,当离开作用域对象会自动销毁而调用析构函数释放资源。

2. 智能指针的特点

  1. 具有 RAII的思想

  2. 向指针一样,可以使其指向对象成员,并且重载了 * 和 -> 运算符

  3. 是对资源的一种管理,不是拥有

注:对于管理的理解

  1. 情况1:当我们使用普通指针进行管理动态内存时,往往会忘记释放内存,这时就会造成内存泄露,此时使用智能指针暂时保管这块内存,使用完了会自动调用析构,完成对资源的回收
  2. 情况2:如下示例,当抛出异常时,就会打断执行流,不会在执行下面的delete工作,这样的情况也是一种内存泄露,当使用智能指针就能很好的避免这样的情况
 void test()
{
    int*_ptr=new int(1);
    if(_ptr)
    {
        throw 1;
    }
    delete _ptr;
}

3. 模拟实现几种智能指针

3.1 AutorPtr
  本质上是一种管理权的转移,当我把我的空间交给你管理后,我自己就置为空,不在管理这块空间

几个缺点

  1. 不要使用auto_ptr保存一个非动态开辟空间的指针,因为在作用域结束的时候,会执行智能指针的析构函数,释放这块空间,但非动态的空间又无法释放;
  2. 不要使用两个auto_ptr指针指向同一个指针
  3. 不要使用auto_ptr指向一个指针数组,因为auto_ptr的析构函数所用的是delete而不是delete[],不匹配;
  4. 不要将auto_ptr储存在容器中,因为赋值和拷贝构造后原指针无法使用。
    注:什么情况下也别使用auto_ptr智能指针。

代码实现:

template<class T>
class AutorPtr
{
public:
    AutorPtr(T* ap)
      :_Ptr(ap)
    {}

    AutorPtr(AutorPtr<T>& ap)//管理权的转移
    {
        _Ptr = ap._Ptr;
        ap._Ptr = NULL;
    }

    AutorPtr& operator= ( AutorPtr<T>& ap)
    {
        if (this != &ap)
        {
            _Ptr = ap._Ptr;//管理权进行转移,自己的指针置为空
            ap._Ptr = NULL;
        }
        return *this;
    }

    ~AutorPtr()
    {
        printf("%p\n", _Ptr);
        delete _Ptr;
    }

    T& operator*()
    {
        return *_Ptr;
    }

    T* operator->()
    {
        return _Ptr;
    }
protected:

    T* _Ptr;
};

void TestAutorptr()
{
    AutorPtr<int> ap1(new int(10));
    AutorPtr<int> ap2 = ap1;
    //*ap1 = 100;//管理权已经转移,ap1管理的空间已经被ap2管理,所以导致空指针解引用
    *ap2 = 200;

    AutorPtr<int> ap3(new int(11));
    ap2 = ap3;
}

3.2 ScopedPtr

  1. 于autoptr最大的不同就是scoped_ptr没有给出拷贝构造和赋值运算符的重载运算符的定义,只给了private下的声明,即表明scoped_ptr智能指针无法使用一个对象创建另一个对象,
  2. 也无法采用赋值的形式。这无疑提升了智能指针的安全性,但是又存在无法“++”、“- -”这些操作,
  3. 与此同时多了“*”、“->”这两种操作。

代码实现:

template<typename T>
class ScopedPtr
{
public:
    explicit ScopedPtr(T *p = 0) 
        :mp(p)
    {}


    ~ScopedPtr()
    {
        delete mp;
    }

    void reset(T *p = 0)//重置
    {
        if (mp != p)
        {
            delete mp;
            mp = p;
        }
    }

    T &operator*() const
    {
        if (mp != 0)
            return *mp;
        else
            throw std::runtime_error("the pointer is null");
    }

    T * operator->() const
    {
        if (mp != 0)
            return mp;
        else
            throw std::runtime_error("the pointer is null");
    }

    T *get() const
    {
        return mp;
    }
    void swap(ScopedPtr &rhs)
    {
        T *temp = mp;
        mp = rhs.mp;
        rhs.mp = temp;
    }
private:
    ScopedPtr(const ScopedPtr& rhs);
    ScopedPtr &operator=(const ScopedPtr& rhs);//防拷贝,只声明不实现
    T *mp;
};


void TestScopedPtr()
{
    ScopedPtr<int> sp1(new int(3));
    //ScopedPtr<int> sp2(sp1);//防拷贝,不能拷贝
}

3.3 SharedPtr

  1. shared_ptr和以上二者的最大区别就是他维护了一个引用计数,用于检测当前对象所管理的指针是否还被其他智能指针使用(必须是shared_ptr管理的智能指针)
  2. 在析构函数时对其引用计数减一,判断是否为0,若为0,则释放这个指针和这个引用计数的空间。
  3. 其实,这个原理就和string类的浅拷贝是一样的。
  4. 这个类型的智能指针在一定程度上让我们的管理得到了很大的便捷和省心。
  5. 可以和其他共同管理这块空间的智能指针一起修改这块空间储存的值,达到“智能”的效果。

代码:

template<class T>
class SharedPtr
{
public:
    SharedPtr(SharedPtr<T>* sp,int* spcount)
        :_ptr(sp)
        , _pCount(spcount)
    {}

    SharedPtr(const SharedPtr<T>& sp)
    {
        _ptr = sp._ptr;
        _pCount = sp._pCount;
        ++(*pCount);
    }

    SharedPtr& opertaor=(const SharedPtr<T>& sp)
    {
        if (this != &sp)
        {
            if (--(*_pCount) == 0)
            {
                delete _ptr;
                delete _pCount;
            }
            _ptr = sp._ptr;
            _pCount = sp._pCount;
            ++(*pCount);
        }
        return *this;
    }

    ~SharedPtr()
    {
        if (--(*pCount) == 0)
        {
            delete _ptr;
            delete _pCount;
        }
    }
    T&operator*()
    {
        return *_ptr;
    }

    T*operator->()
    {
        return _ptr;
    }
private:
    T* _ptr;
    int* _pCount;
};

3.4 弱指针—weak_ptr

  先来说说弱指针的作用:用于专门解决SharedPtr循环引用的问题
  
两个问题:
 
  1. 什么是循环引用?

场景:

    struct ListNode
{
    SharedPtr<ListNode> _next;
    SharedPtr<ListNode> _prev;

    ListNode()
        :_prev(NULL)
        ,_next(NULL)
    {}

    ~ListNode()
    {
        cout << "delete ListNode" << endl;
    }
};

void TestSharePtrCycle()
{
    SharedPtr<ListNode> cur = new ListNode;
    SharedPtr<ListNode> next = new ListNode;
    cur->_next = next;
    next->_prev = cur;
}

分析场景:

这里写图片描述

  2. 什么是弱指针

弱指针是指当指针指向原来空间时,引用计数不在进行加1
注:不是管理和释放当前的指针,而是避免了一次引用计数

3.如何解决?

使用弱指针来负责cur->next 和next->prev的指向时,使其引用计数不在加1,这样两个节点的引用计数就是1,不会再增加,故此释放时,会将其节点进行释放

代码:

template<class T>
class WeakPtr;

template<class T>
class SharedPtr
{
    friend class WeakPtr<T>;
public:
    SharedPtr(T* ptr)
        :_ptr(ptr)
        , _pCount(new int(1))
    {}

    SharedPtr(const SharedPtr<T>& sp)
    {
        _ptr = sp._ptr;
        _pCount = sp._pCount;
        (*_pCount)++;
    }

    // sp1 = sp2;
    SharedPtr<T>& operator=(const SharedPtr<T>& sp)
    {
        if (this != &sp)
        {
            if (--(*_pCount) == 0)
            {
                delete _ptr;
                delete _pCount;
            }

            _pCount = sp._pCount;
            _ptr = sp._ptr;
            (*_pCount)++;
        }

        return *this;
    }

    ~SharedPtr()
    {
        //cout<<"~SharedPtr()"<<endl;
        if (--(*_pCount) == 0)
        {
            if (_ptr)
            {
                printf("ptr:%p\n", _ptr);
                delete _ptr;
            }
            delete _pCount;
        }
    }

    T& operator*()
    {
        return *_ptr;
    }

    T* operator->()
    {
        return _ptr;
    }

protected:
    T* _ptr;
    int* _pCount;
};

// 专门解决SharedPtr的循环引用
// 弱指针是指当指针指向原来空间时,引用计数不在进行加1,释放时不在等待,故此解决了循环引用的问题
// 不是管理和释放当前的指针,而是避免了一次引用计数
template<class T>
class WeakPtr
{
public:
    WeakPtr(SharedPtr<T>& sp)//可以将sharedptr的对像传给他,匿名对象
        :_ptr(sp._ptr)       //这里将sharedptr的对象给weakptr(弱指针)进行初始化,此时弱指针指向的是sharedptr的对象
    {}

    WeakPtr()
        :_ptr(NULL)
    {}

    T& operator*()
    {
        return *_ptr;
    }

    T* operator->()
    {
        return _ptr;
    }
protected:
    T* _ptr;
};




struct ListNode
{
    WeakPtr<ListNode> _next;
    WeakPtr<ListNode> _prev;

    //ListNode()
    //  :_prev(NULL)
    //  ,_next(NULL)
    //{}

    ~ListNode()
    {
        cout << "delete ListNode" << endl;
    }
};

void TestSharePtrCycle()
{
    SharedPtr<ListNode> cur = new ListNode;//使用弱指针
    SharedPtr<ListNode> next = new ListNode;
    cur->_next = next;//weakptr接收的是sharedptr的对象
    next->_prev = cur;
}

注:关于解决引用计数的具体步骤,可参考链接:
(https://blog.csdn.net/zhourong0511/article/details/80315961)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++:智能指针及其实现原理 的相关文章

  • C++ 维护子类对象的混合集合

    如果我在这里错过了一个相当基本的概念 我很抱歉 但我正在尝试弄清楚如何维护多个类类型的集合 所有类类型都派生自同一个父类 并且在检索它们时仍然可以访问它们的特定于子类的方法从集合中 作为上下文 我有一个基类 BaseClass 和许多类 例
  • 为什么在连接两个字符串时 Python 比 C 更快?

    目前我想比较 Python 和 C 用来处理字符串的速度 我认为 C 应该比 Python 提供更好的性能 然而 我得到了完全相反的结果 这是 C 程序 include
  • 使用 lambda 表达式注册类型

    我想知道如何在 UnityContainer 中实现这样的功能 container RegisterType
  • 如何在多线程C++ 17程序中交换两个指针?

    我有两个指针 pA 和 pB 它们指向两个大的哈希映射对象 当pB指向的哈希图完全更新后 我想交换pB和pA 在C 17中 如何快速且线程安全地交换它们 原子 我是 c 17 的新手 2个指针的原子无等待交换可以通过以下方式实现 inclu
  • 如何判断计算机是否已重新启动?

    我曾经使用过一个命令行 SMTP 邮件程序 作为试用版的限制 它允许您在每个 Windows 会话中最多接收 10 封电子邮件 如果您重新启动计算机 您可能还会收到 10 个以上 我认为这种共享软件破坏非常巧妙 我想在我的应用程序中复制它
  • 如何填充 ToolStripComboBox?

    我发现它很难将数据绑定到ToolStripComboBox 好像没有这个ValueMember and DisplayMember特性 怎么绑定呢 访问toolstripcombobox中包装的组合框并访问其ValueMember Disp
  • 如何使用 Castle Windsor 将对象注入到 WCF IErrorHandler 实现中?

    我正在使用 WCF 开发一组服务 该应用程序正在使用 Castle Windsor 进行依赖注入 我添加了一个IErrorHandler通过属性添加到服务的实现 到目前为止一切正常 这IErrorHandler对象 一个名为FaultHan
  • Visual Studio 在构建后显示假错误

    我使用的是 Visual Studio 2017 构建后 sln在调试模式下 我收到错误 但是 当我通过双击错误列表选项卡中的错误来访问错误时 错误会从页面中消失 并且错误数量也会减少 我不太确定这种行为以及为什么会发生这种情况 有超过 2
  • unordered_map 中字符串的 C++ 哈希函数

    看起来 C 标准库中没有字符串的哈希函数 这是真的 在任何 c 编译器上使用字符串作为 unordered map 中的键的工作示例是什么 C STL提供模板专业化 http en cppreference com w cpp string
  • 使用 GCP 的数据存储区时如何区分代码是在模拟器中运行还是在 GKE 中运行

    按照中给出的说明进行操作后 我不确定是否遗漏了任何内容https cloud google com datastore docs tools datastore emulator https cloud google com datasto
  • 告诉 Nancy 将枚举序列化为字符串

    Nancy 默认情况下在生成 JSON 响应时将枚举序列化为整数 我需要将枚举序列化为字符串 有一种方法可以通过创建来自定义 Nancy 的 JSON 序列化JavaScript 原始转换器 https github com NancyFx
  • 将 Long 转换为 DateTime 从 C# 日期到 Java 日期

    我一直尝试用Java读取二进制文件 而二进制文件是用C 编写的 其中一些数据包含日期时间数据 当 DateTime 数据写入文件 以二进制形式 时 它使用DateTime ToBinary on C 为了读取 DateTime 数据 它将首
  • 如何在 C 中安全地声明 16 位字符串文字?

    我知道已经有一个标准方法 前缀为L wchar t test literal L Test 问题是wchar t不保证是16位 但是对于我的项目 我需要16位wchar t 我还想避免通过的要求 fshort wchar 那么 C 不是 C
  • C++ 中的双精度型数字

    尽管内部表示有 17 位 但 IEE754 64 位 浮点应该正确表示 15 位有效数字 有没有办法强制第 16 位和第 17 位为零 Ref http msdn microsoft com en us library system dou
  • 在屏幕上获取字符

    我浏览了 NCurses 函数列表 似乎找不到返回已打印在屏幕上的字符的函数 每个字符单元格中存储的字符是否有可访问的值 如果没有的话Windows终端有类似的功能吗 我想用它来替换屏幕上某个值的所有字符 例如 所有a s 具有不同的特征
  • 打印大型 WPF 用户控件

    我有一个巨大的数据 我想使用 WPF 打印 我发现WPF提供了一个PrintDialog PrintVisual用于打印派生的任何 WPF 控件的方法Visual class PrintVisual只会打印一页 因此我需要缩放控件以适合页面
  • 这个可变参数模板示例有什么问题?

    基类是 include
  • 使用 C 在 OS X 中获取其他进程的 argv

    我想获得其他进程的argv 例如ps 我使用的是在 Intel 或 PowerPC 上运行的 Mac OS X 10 4 11 首先 我阅读了 ps 和 man kvm 的代码 然后编写了一些 C 代码 include
  • GCC 的“-Wl,option”和“-Xlinker option”语法之间有区别吗?

    我一直在查看一些配置文件 并且看到它们都被使用 尽管在不同的体系结构上 如果您在 Linux 机器上使用 GCC 将选项传递给链接器的两种语法之间有区别吗 据我所知 阅读 GCC 手册时 他们的解释几乎相同 From man gcc Xli
  • 如何使用 C++11 using 语法键入定义函数指针?

    我想写这个 typedef void FunctionPtr using using 我该怎么做呢 它具有类似的语法 只不过您从指针中删除了标识符 using FunctionPtr void 这是一个Example http ideone

随机推荐

  • JSP中获取参数的3中方法

    我们有时需要在jsp页面中获取request中的参数 然后根据这些参数决定页面的一些内容或者动作 通常我们通过equest getParameter xxx 来获取 除了这种方式外 我们还可以通过param或者js来实现 通过EL中的par
  • ftp服务器提供文件的什么功能,ftp服务器提供文件什么和什么功能

    ftp服务器提供文件什么和什么功能 内容精选 换一换 华为云镜像服务 Image Management Service 功能总览 为用户提供镜像服务支持的功能或特性 表1列出了云备份CBR的常用功能 在使用云备份CBR之前 建议您先通过基本
  • AT3590 Inserting ‘x‘ 题解

    本题是一道双指针的模拟题 题意 给你一个字符串 s s s 你可以在 s s s 的任意位置插入 x x
  • 好的习惯----程序员成长之路(from老大邮件)

    对于好程序员 有很多好的习惯 为什么要把这个习惯放在第一个呢 有很多人如果阅读过 高效能人士的七个习惯 其中第一个习惯就是积极主动 如果从这个角度来看 我把解决解决每一个问题放在首位从理论上是完全没问题的 但我要说说我们程序员独特的地方 所
  • [4G+5G专题-133]: 部署 - 4G/5G常见的室内部署方案

    作者主页 文火冰糖的硅基工坊 文火冰糖 王文兵 的博客 文火冰糖的硅基工坊 CSDN博客 本文网址 https blog csdn net HiWangWenBing article details 121554032 目录 第1章 概述
  • 总结】python sklearn模型中random_state参数的意义

    这是在决策树CART模型时 遇到的 random state 是为了固定随机状态的 主要用在随机数据集 数据集的随机划分 设置决策树模型参数 设置随机森林模型参数 random state 取值大小可以是任意一个整数 在调参缓解 只要保证其
  • 基于Docker的JMeter分布式压测实战讲解

    一个JMeter实例可能无法产生足够的负载来对你的应用程序进行压力测试 如本网站所示 一个JMeter实例将能够控制许多其他的远程JMeter实例 并对你的应用程序产生更大的负载 JMeter使用Java RMI 远程方法调用 来与分布式网
  • 即将离开CSDN,转其他平台

    CSDN的几大作死操作 1 同质化太特么严重了 博客抄来抄去的 内容审核形同虚设 经常搜一个问题 从第一条到最后一条都是一模一样的内容 2 资源付费 资源付费本身是没有任何问题的 但是CSDN里面有几个有用的资源 很多大家花了钱一下载 发现
  • windows凭据密码怎么查看_管理Windows访问凭证,快速访问局域网上的共享资源

    内部网访问其他电脑的共享资源 基本上需要输入访问对方电脑资源允许的账号和密码 在第一次的访问中选择保存凭据后 以后访问就不要输入相应的账号和密码了 但也会出现因修改相关的访问密码或者取消了访问账号的改变 这样就会出现凭据失效的情况 下面介绍
  • 类似-Xms、-Xmn这些参数的含义:

    类似 Xms Xmn这些参数的含义 答 堆内存分配 JVM初始分配的内存由 Xms指定 默认是物理内存的1 64 JVM最大分配的内存由 Xmx指定 默认是物理内存的1 4 默认空余堆内存小于40 时 JVM就会增大堆直到 Xmx的最大限制
  • Python通过ARIMA模型进行时间序列分析预测

    ARIMA模型预测 时间序列分析预测就是在已有的和时间有关的数据序列的基础上构建其数据模型并预测其未来的数据 例如航空公司的一年内每日乘客数量 某个地区的人流量 这些数据往往具有周期性的规律 如下图所示 有的数据呈现出简单的周期性循环 有的
  • Linux嵌入式学习---c语言之循环结构

    Linux嵌入式学习 c语言之循环结构 一 while语句循环 1 1一般形式 1 2累加求和 二 do while语句循环 2 1do while语句一般形式 2 2do while语句特点 三 for语句循环 3 1for语句的一般形式
  • vue-resource请求数据的使用方法

    vue resource vue js关于客户端请求数据的官方插件 使用vue resource请求数据的步骤 1 安装vue resource插件 记得添加 save 若安装淘宝镜像用cnpm npm install vue resour
  • [蓝桥杯2023初赛] 整数删除

    给定一个长度为 N 的整数数列 A1 A2 AN 你要重复以下操作 K 次 每次选择数列中最小的整数 如果最小值不止一个 选择最靠前的 将其删除 并把与它相邻的整数加上被删除的数值 输出 K 次操作后的序列 输入格式 第一行包含两个整数 N
  • vscode:visual studio code 调试php

    简介 php是动态语言没有调试器的话排错起来很是麻烦 vscode可以说是程序员的福音 启动速度快 插件越来越多 跨平台 现在说一下vscode上调试php文件 所需文件 xampp 集成服务器 vscode Xdebug php debu
  • Rightware的Kanzi界面很快你的全液晶汽车仪表盘

    锋影 e mail 174176320 qq com 这是一个屏幕在行动的Kanzi UI编辑器 这是说 汽车仪表板没有显著在过去的几十年里发展公平 不知怎么的 我觉得喜欢的东西是会改变的 但什么也没做 至少在一个大的方式 当日产GTR天际
  • 面试必问的Spring IoC与Spring AOP面试题,你能get到几问?

    Spring IoC Q1 IoC 是什么 Q2 IoC 容器初始化过程 Q3 依赖注入的实现方法有哪些 Q4 依赖注入的相关注解 Q5 依赖注入的过程 Q6 Bean 的生命周期 Q7 Bean 的作用范围 Q8 如何通过 XML 方式创
  • (含源码)「自然语言处理(NLP)」RoBERTa&&XLNet&&语言模型&&问答系统训练

    来源 AINLPer 微信公众号 每日更新 编辑 ShuYini 校稿 ShuYini 时间 2020 07 27 引言 本次内容主要包括 稳健优化Bert模型 RoBERTa 自回归预训练模型 XLNet 无监督多任务学习语言模型 生成预
  • 【BT 协议】HFP 协议

    原文链接 https blog csdn net shichaog article details 52123439 HFP Hands free Profile 让蓝牙设备可以控制电话 如接听 挂断 拒接 语音拨号等 拒接 语音拨号要视蓝
  • C++:智能指针及其实现原理

    更多C 知识点 C 目录索引 1 RAII思想 定义一个类来封装资源的分配与释放 构造函数中完成资源的分配及初始化 析构函数中完成资源的清理 可以保证资源的正确初始化和释放 如果对象是用声明的方式在栈上创建局部对象 那么RAII机制就会正常