【C++ 学习 ㊱】- 智能指针详解

news2025/1/11 15:41:51

目录

一、为什么需要智能指针?

二、智能指针的原理及使用

三、auto_ptr

3.1 - 基本使用

3.2 - 模拟实现

四、unique_ptr

4.1 - 基本使用

4.2 - 模拟实现

五、shared_ptr

5.1 - 基本使用

5.2 - 模拟实现

六、weak_ptr

6.1 - shared_ptr 的循环引用问题

6.2 - 模拟实现

七、定制删除器



一、为什么需要智能指针?

问题引入:

#include <iostream>
using namespace std;
​
int division(int x, int y)
{
    if (y == 0)
        throw "Division by zero condition!";
    else
        return x / y;
}
​
void func()
{
    string* p1 = new string("hello");
    pair<string, string>* p2 = new pair<string, string>{ "hello", "你好" };
​
    int a = 0, b = 0;
    cin >> a >> b;
    cout << division(a, b) << endl;
​
    delete p1;
    delete p2;
}
​
int main()
{
    try {
        func();
    }
    catch (const char* errmsg) {
        cout << errmsg << endl;
    }
    catch (...) {
        cout << "Unknow exception" << endl;
    }
    return 0;
}

调用 func 函数:

  1. 如果在执行 string* p1 = new string("hello"); 语句的过程中抛出了异常,那么在 main 函数中会捕获到抛出的异常;

  2. 如果在执行 pair<string, string>* p2 = new pair<string, string>{ "hello", "你好" }; 语句的过程中抛出了异常,那么在 main 函数中会捕获到抛出的异常,但是 p1 指向的动态分配的内存没有被释放,最终会造成内存泄漏

  3. 如果在调用 division(a, b) 函数的过程中抛出了异常,那么在 main 函数中会捕获到抛出的异常,但是 p1 以及 p2 指向的动态分配的内存都没有被释放,最终也会造成内存泄漏


二、智能指针的原理及使用

RAII(Resource Acquisition Is Initialization,资源获取即初始化)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术

在对象构造时获取资源,对资源的访问在对象生命周期内始终有效;在对象析构时,即对象生命周期结束时,释放资源

SmartPtr.h

#pragma once
​
namespace yzz
{
    template<class T>
    class SmartPtr
    {
    public:
        SmartPtr(T* ptr = nullptr) : _ptr(ptr) 
        { }
​
        ~SmartPtr()
        {
            if (_ptr)
            {
                delete _ptr;
                _ptr = nullptr;
            }
        }
        
        T& operator*()
        {
            return *_ptr;
        }
​
        T* operator->()
        {
            return _ptr;
        }
    private:
        T* _ptr;
    };
}

test.cpp

#include <iostream>
#include "SmartPtr.h"
using namespace std;
​
int division(int x, int y)
{
    if (y == 0)
        throw "Division by zero condition!";
    else
        return x / y;
}
​
void func()
{
    yzz::SmartPtr<string> sp1 = new string("hello");
    cout << *sp1 << endl;
​
    yzz::SmartPtr<pair<string, string>> sp2 = new pair<string, string>{ "hello", "你好"};
    cout << (*sp2).first << " : " << (*sp2).second << endl;
    cout << sp2->first << " : " << sp2->second << endl;  
    // 为了可读性,编译器将 sp2->->first/second 优化成了 sp2->first/second
​
    int a = 0, b = 0;
    cin >> a >> b;
    cout << division(a, b) << endl;
    // 在 func 函数中,无论有没有异常抛出,
    // func 函数结束后,都会自动调用 sp1 以及 sp2 对象的析构函数释放动态分配的内存,
    // 不再需要手动 delete
}
​
int main()
{
    try {
        func();
    }
    catch (const char* errmsg) {
        cout << errmsg << endl;
    }
    catch (...) {
        cout << "Unknow exception" << endl;
    }
    return 0;
}


三、auto_ptr

C++98/03 标准中提供了 auto_ptr 智能指针

3.1 - 基本使用

#include <memory>
#include <iostream>
using namespace std;
​
class A
{
public:
    A(int x = 0) : _i(x)
    {
        cout << "A(int x = 0)" << endl;
    }
​
    ~A()
    {
        cout << "~A()" << endl;
    }
​
    int _i;
};
​
int main()
{
    auto_ptr<A> ap1(new A(1));
    auto_ptr<A> ap2(new A(2));
​
    auto_ptr<A> ap3(ap1);  // 管理权转移
    auto_ptr<A> ap4(new A(4));
    ap4 = ap2;  // 管理权转移
​
    // cout << ap1->_i << endl;  // error
    // cout << ap2->_i << endl;  // error
    cout << ap3->_i << endl;  // 1
    cout << ap4->_i << endl;  // 2
    return 0;
}

3.2 - 模拟实现

namespace yzz
{
    template<class T>
    class auto_ptr
    {
    public:
        auto_ptr(T* ptr = nullptr) : _ptr(ptr)
        { }
​
        ~auto_ptr()
        {
            if (_ptr)
            {
                delete _ptr;
                _ptr = nullptr;
            }
        }
​
        auto_ptr(auto_ptr<T>& ap)
            : _ptr(ap._ptr)
        {
            ap._ptr = nullptr;
        }
​
        auto_ptr<T>& operator=(auto_ptr<T>& ap)
        {
            if (this != &ap)
            {
                if (_ptr)
                    delete _ptr;
​
                _ptr = ap._ptr;
                ap._ptr = nullptr;
            }
            return *this;
        }
​
        T& operator*()
        {
            return *_ptr;
        }
​
        T* operator->()
        {
            return _ptr;
        }
    private:
        T* _ptr;
    };
}


四、unique_ptr

C++11 标准废弃了 auto_ptr,新增了 unique_ptr、shared_ptr 以及 weak_ptr 这三个智能指针

4.1 - 基本使用

#include <memory>
#include <iostream>
using namespace std;
​
class A
{
public:
    A(int x = 0) : _i(x)
    {
        cout << "A(int x = 0)" << endl;
    }
​
    ~A()
    {
        cout << "~A()" << endl;
    }
​
    int _i;
};
​
int main()
{
    unique_ptr<A> up1(new A(1));
    unique_ptr<A> up2(new A(2));
​
    // unique_ptr<A> up3(up1);  // error
    unique_ptr<A> up4(new A(4));
    // up4 = up2;  // error
    return 0;
}

4.2 - 模拟实现

从上面的例子中可以看出,unique_ptr 的实现原理就是简单粗暴的防拷贝

namespace yzz
{
    template<class T>
    class unique_ptr
    {
    public:
        unique_ptr(T* ptr = nullptr) : _ptr(ptr)
        { }
​
        ~unique_ptr()
        {
            if (_ptr)
            {
                delete _ptr;
                _ptr = nullptr;
            }
        }
​
        unique_ptr(const unique_ptr<T>& up) = delete;
        unique_ptr<T>& operator=(const unique_ptr<T>& up) = delete;
​
        T& operator*()
        {
            return *_ptr;
        }
​
        T* operator->()
        {
            return _ptr;
        }
    private:
        T* _ptr;
    };
}


五、shared_ptr

5.1 - 基本使用

#include <memory>
#include <iostream>
using namespace std;
​
class A
{
public:
    A(int x = 0) : _i(x)
    {
        cout << "A(int x = 0)" << endl;
    }
​
    ~A()
    {
        cout << "~A()" << endl;
    }
​
    int _i;
};
​
int main()
{
    shared_ptr<A> sp1(new A(1));
    shared_ptr<A> sp2(new A(2));
    cout << sp1.use_count() << endl;  // 1
    cout << sp2.use_count() << endl;  // 1
    cout << "-------------" << endl;
​
    shared_ptr<A> sp3(sp1);
    cout << sp1.use_count() << endl;  // 2
    cout << sp3.use_count() << endl; // 2
    sp3->_i *= 10;
    cout << sp1->_i << endl;  // 10
    cout << sp3->_i << endl;  // 10
    cout << "-------------" << endl;
​
    shared_ptr<A> sp4(new A(4));
    sp4 = sp2;
    cout << sp2.use_count() << endl;  // 2
    cout << sp4.use_count() << endl; // 2
    sp4->_i *= 10;
    cout << sp2->_i << endl;  // 20
    cout << sp4->_i << endl;  // 20
    return 0;
}

5.2 - 模拟实现

shared_ptr 的实现原理:通过引用计数的方式实现多个 shared_ptr<T> 类对象之间共享资源

引用计数:用来记录资源使用者的个数。在构造时,将资源的计数给成 1,每增加一个对象使用该资源,就给计数增加 1,当某个对象被销毁时,先给该计数减 1,然后再检查是否需要释放资源,如果计数为 0,说明该对象是资源的最后一个使用者,于是将资源释放,否则就不能释放,因为还有其他对象在使用该资源

namespace yzz
{
    template<class T>
    class shared_ptr
    {
    public:
        shared_ptr(T* ptr = nullptr)
            : _ptr(ptr), _pCnt(new int(1))
        { }
​
        ~shared_ptr()
        {
            if (--(*_pCnt) == 0)
            {
                if (_ptr)
                {
                    delete _ptr;
                    _ptr = nullptr;
                }
                delete _pCnt;
            }
        }
​
        shared_ptr(const shared_ptr<T>& sp)
            : _ptr(sp._ptr), _pCnt(sp._pCnt)
        {
            ++(*_pCnt);
        }
​
        shared_ptr<T>& operator=(const shared_ptr<T>& sp)
        {
            if (_ptr != sp._ptr)
            {
                if (--(*_pCnt) == 0)
                {
                    if (_ptr)
                        delete _ptr;
​
                    delete _pCnt;
                }
​
                _ptr = sp._ptr;
                _pCnt = sp._pCnt;
                ++(*_pCnt);
            }
            return *this;
        }
​
        T& operator*()
        {
            return *_ptr;
        }
​
        T* operator->()
        {
            return _ptr;
        }
​
        int use_count() const
        {
            return *_pCnt;
        }
​
        T* get() const
        {
            return _ptr;
        }
    private:
        T* _ptr;
        int* _pCnt;
    };
}

 


六、weak_ptr

6.1 - shared_ptr 的循环引用问题

#include <memory>
#include <iostream>
using namespace std;
​
class A
{
public:
    A(int x = 0) : _i(x)
    {
        cout << "A(int x = 0)" << endl;
    }
​
    ~A()
    {
        cout << "~A()" << endl;
    }
​
    int _i;
};
​
struct ListNode
{
    A _val;
    shared_ptr<ListNode> _prev;
    shared_ptr<ListNode> _next;
};
​
int main()
{
    shared_ptr<ListNode> sp1(new ListNode);
    shared_ptr<ListNode> sp2(new ListNode);
    // A(int x = 0)
    // A(int x = 0)
    cout << sp1.use_count() << endl;  // 1
    cout << sp2.use_count() << endl;  // 1
​
    sp1->_next = sp2;
    sp2->_prev = sp1;
    cout << sp1.use_count() << endl;  // 2
    cout << sp2.use_count() << endl;  // 2
    // 出现内存泄漏问题
    return 0;
}

解决方案

#include <memory>
#include <iostream>
using namespace std;
​
class A
{
public:
    A(int x = 0) : _i(x)
    {
        cout << "A(int x = 0)" << endl;
    }
​
    ~A()
    {
        cout << "~A()" << endl;
    }
​
    int _i;
};
​
struct ListNode
{
    A _val;
    // weak_ptr 不增加引用计数,并且在可以访问资源的同时,不参与释放资源
    weak_ptr<ListNode> _prev;
    weak_ptr<ListNode> _next;
};
 
int main()
{
    shared_ptr<ListNode> sp1(new ListNode);
    shared_ptr<ListNode> sp2(new ListNode);
    // A(int x = 0)
    // A(int x = 0)
    cout << sp1.use_count() << endl;  // 1
    cout << sp2.use_count() << endl;  // 1
    
    sp1->_next = sp2;
    sp2->_prev = sp1;
    cout << sp1.use_count() << endl;  // 1
    cout << sp2.use_count() << endl;  // 1
    // ~A()
    // ~A()
    return 0;
}

6.2 - 模拟实现

namespace yzz
{
    template<class T>
    class weak_ptr
    {
    public:
        weak_ptr() : _ptr(nullptr)
        { }
​
        weak_ptr(const shared_ptr<T>& sp)
            : _ptr(sp.get())
        { }
​
        weak_ptr<T>& operator=(const shared_ptr<T>& sp)
        {
            _ptr = sp.get();
            return *this;
        }
​
        T& operator*()
        {
            return *_ptr;
        }
​
        T* operator->()
        {
            return _ptr;
        }
    private:
        T* _ptr;
    };
}


七、定制删除器

template<class T>
struct DeleteArrayFunc
{
    void operator()(T* ptr)
    {
        delete[] ptr;
    }
};
​
template<class T>
struct FreeFunc
{
    void operator()(T* ptr)
    {
        free(ptr);
    }
};
​
int main()
{
    shared_ptr<A> sp1(new A(1));  // ok
​
    // shared_ptr<A> sp2(new A[5]);   // error
    shared_ptr<A> sp2(new A[5], DeleteArrayFunc<A>());   // ok
​
    // shared_ptr<A> sp3((A*)malloc(sizeof(A)));  // error
    shared_ptr<A> sp3((A*)malloc(sizeof(A)), FreeFunc<A>());  // ok
​
    // shared_ptr<FILE> sp4(fopen("test.cpp", "r"));  // error
    shared_ptr<FILE> sp4(fopen("test.cpp", "r"), 
                         [](FILE* fp) { fclose(fp); });  // error
    return 0;
}

shared_ptr 的模拟实现

#include <functional>
​
namespace yzz
{
    template<class T>
    class shared_ptr
    {
    public:
        shared_ptr(T* ptr = nullptr)
            : _ptr(ptr), _pCnt(new int(1)), _del([](T* ptr) { delete ptr; })
        { }
​
        template<class D>
        shared_ptr(T* ptr, D del)
            : _ptr(ptr), _pCnt(new int(1)), _del(del)
        { }
​
        ~shared_ptr()
        {
            if (--(*_pCnt) == 0)
            {
                if (_ptr)
                {
                    _del(_ptr);
                    _ptr = nullptr;
                }
                delete _pCnt;
            }
        }
        
        // ... ...
    private:
        T* _ptr;
        int* _pCnt;
        std::function<void(T*)> _del;
    };
}

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

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

相关文章

【Python小程序】求解2 * 2矩阵的逆矩阵

一、内容简介 使用Python求解2 * 2矩阵的逆矩阵。 二、求解方法 我们使用邻接矩阵法来求解2 * 2矩阵的逆矩阵。 det(A): 矩阵A的行列式 adj(A): 矩阵A的邻接矩阵 对于2*2矩阵A 我们有 三、Python代码 基于上述求解方法&#xff0c;我们可以写出Python代码如下&#xff…

行情分析——加密货币市场大盘走势(11.13)

大饼上涨太快&#xff0c;又开始震荡&#xff0c;但上不去&#xff0c;所以目前来看差不多要做回踩动作&#xff0c;入场空单性价比较高。而且从MACD日线来看&#xff0c;也是进入空头趋势&#xff0c;RSI&#xff08;14&#xff09;也是进入了超买区间&#xff0c;值得入手空单…

乡镇村污水处理智慧水务智能监管平台,助力污水监管智慧化、高效化

一、背景与需求 随着城市化进程的加速&#xff0c;排放的污水量也日益增加&#xff0c;导致水污染严重。深入打好污染防治攻坚战的重要抓手&#xff0c;对于改善城镇人居环境&#xff0c;推进城市治理体系和治理能力现代化&#xff0c;加快生态文明建设&#xff0c;推动高质量…

拼多多商品详情API接口接入流程如下:

拼多多商品详情API接口可以用于获取拼多多商品的具体信息&#xff0c;包括商品ID、商品名称、价格、销量、评价等。以下是使用拼多多商品详情API接口的步骤&#xff1a; 进入拼多多开放平台&#xff0c;注册并登录账号。在开放平台页面中&#xff0c;找到“商品详情”或“商品…

基于SSM+Vue的电子商城的设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…

基因检测技术的发展与创新:安全文件数据传输的重要作用

基因是生命的密码&#xff0c;它决定了我们的身体特征、健康状况、疾病风险等。随着基因检测技术的高速发展&#xff0c;我们可以通过对基因进行测序、分析和解读&#xff0c;更深入地认识自己&#xff0c;预防和治疗各种遗传性疾病&#xff0c;甚至实现个性化医疗和精准健康管…

动态调整学习率Lr

动态调整学习率Lr 0 引入1 代码例程1.1 工作方式解释 2 动态调整学习率的几种方法2.1 lr_scheduler.LambdaLR2.2 lr_scheduler.StepLR2.3 lr_scheduler.MultiStepLR2.4 lr_scheduler.ExponentialLR2.2.5 lr_scheduler.CosineAnnealingLR2.6 lr_scheduler.ReduceLROnPlateau2.7 …

ASK、PSK、FSK的调制与解调

ASK、PSK、FSK的调制与解调 本文主要涉及数字信号的调制与解调&#xff0c;内容包括&#xff1a;2ASK、2PSK、2FSK的调制与解调以及频谱分析 关于通信原理还有其他文章可参考&#xff1a; 1、信息量、码元、比特、码元速率、信息速率详细解析——实例分析 2、模拟系统的AM信号的…

2023最新版JavaSE教程——第6天:面向对象编程(基础)

目录 一、面向对象编程概述(了解)1.1 程序设计的思路1.2 由实际问题考虑如何设计程序1.3 如何掌握这种思想&#xff1f; 二、Java语言的基本元素&#xff1a;类和对象2.1 引入2.2 类和对象概述2.3 类的成员概述2.4 面向对象完成功能的三步骤(重要)2.4.1 步骤1&#xff1a;类的定…

数据分类分级方法及典型应用场景

1 2021-09-29 来源&#xff1a;数据学堂 [打印本稿][字号 大 中小] 《数据安全法》的第二十一条明确规定了由国家建立数据分类分级保护制度&#xff0c;根据数据在经济社会发展中的重要程度&#xff0c;以及一旦遭到篡改、破坏、泄露或者非法获取、非法利用&#xff0c;对国…

Flutter实践二:repository模式

1.repository 几乎所有的APP&#xff0c;从简单的到最复杂的&#xff0c;在它们的架构里几乎都包括状态管理和数据源这两部分。状态管理常见的有Bloc、Cubit、Provider、ViewModel等&#xff0c;数据源则是一些直接和数据库或者网络客户端进行交互&#xff0c;取得相应的数据&…

【开源】基于Vue和SpringBoot的智能停车场管理系统

项目编号&#xff1a; S 005 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S005&#xff0c;文末获取源码。} 项目编号&#xff1a;S005&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、研究内容A. 车主端功能B. 停车工作人员功能C. 系…

智能井盖传感器具有什么效果?

智能井盖传感器与智慧城市之间有着密切的关联&#xff0c;两者之间属于相辅相成的状态&#xff0c;对于城市的现代化和城市生命线建设有助力作用。智能井盖传感器是其中一个重要的组成环节&#xff0c;它们帮助城市改变原有的生活和生态环境&#xff0c;为政府部门完善城市基础…

Linux(命令)——结合实际场景的命令 查找Java安装位置命令

前言 在内卷的时代&#xff0c;作为开发的程序员也需要懂一些Linux相关命令。 本篇博客结合实际应用常见&#xff0c;记录Linux命令相关的使用&#xff0c;持续更新&#xff0c;希望对你有帮助。 目录 前言引出一、查找Java安装位置命令1、使用which命令2、使用find命令3、查…

从0到0.01入门React | 006.精选 React 面试题

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…

ESP32 Arduino引脚分配参考:您应该使用哪些 GPIO 引脚?

ESP32 芯片有 48 个引脚&#xff0c;具有多种功能。并非所有 ESP32 开发板中的所有引脚都暴露出来&#xff0c;有些引脚无法使用。 关于如何使用 ESP32 GPIO 有很多问题。您应该使用什么引脚&#xff1f;您应该避免在项目中使用哪些引脚&#xff1f;这篇文章旨在成为 ESP32 GP…

【机器学习】 朴素贝叶斯算法:原理、实例应用(文档分类预测)

1. 算法原理 1.1 朴素贝叶斯方法 朴素贝叶斯方法涉及一些概率论知识&#xff0c;我们先来复习一下。 联合概率&#xff1a;包含多个条件&#xff0c;并且所有的条件同时成立的概率&#xff0c;公式为&#xff1a; 条件概率&#xff1a;事件A在另一个事件B已经发生的前提下发…

零代码搭建:无需编程基础,轻松搭建数据自己的能源监测管理平台

零代码搭建能源管理平台&#xff0c;其核心是通过使用图形用户界面和可视化建模工具&#xff0c;来减少编写代码的工作量以及技能要求。平台拥有丰富的预定义组件&#xff0c;可以帮助管理人员快速构建应用程序。并可自定义区域框架&#xff0c;在搭建自己区域时&#xff0c;能…

视频监控系统EasyCVR平台播放告警录像时,播放器显示不全是什么原因?

防视频监控/视频集中存储/云存储/磁盘阵列EasyCVR平台可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。平台既具备传统安防…

解密.devos病毒:如何守护你的数据免受.devos病毒威胁?

引言&#xff1a; 近来&#xff0c;网络安全威胁再次升级&#xff0c;其中一个备受关注的恶意软件就是.devos病毒。这种病毒通过加密你的数据文件&#xff0c;然后勒索赎金&#xff0c;给用户带来了巨大的威胁。本文将介绍如何应对被.devos病毒加密的数据文件&#xff0c;以及…