C++智能指针(二十)

news2024/11/18 5:59:02

一.RAII(Resource Acquisition Is Initialization

RAII资源获取即初始化,RAII的思想就是在构造时初始化资源,或者托管已经构造的资源。在析构的时候释放资源。一般不允许复制或赋值,并且提供若干的资源访问的方法。比如:我们创建一个文件读写的类,当我们通过类创建一个栈对象时,并初始化传入要打开文件的指针,当我栈对象的生命周期结束会自动调用析构函数将文件关闭,这就为我们解决一些文件未close的安全问题。

#include <iostream>
#include <string>
#include <string.h>
using std::endl;
using std::cout;
using std::string;

class safeFile
{
private:
    FILE* _fp;
public:
    safeFile(FILE* fp)
    :_fp(fp)
    {
        cout<<"safeFile(FILE* fp)"<<endl;
    }

 //一般不允许赋值和复制     删除或者设置成私有的
    safeFile(const safeFile& rhs)=delete;

    safeFile& operator=(const safeFile& rhs)=delete;

    void write(const string& msg)
    {
        fwrite(msg.c_str(),1,strlen(msg.c_str()),_fp);
        cout<<"void write(const string& msg)"<<endl;
    }
    
    ~safeFile()
    {
        if(_fp)
        {
            fclose(_fp);
            cout<<"~safeFile()"<<endl;
        }
    }
};


int main()
{
    safeFile sf(fopen("test.txt","a+"));//sf 是栈对象销毁时自动执行关文件的操作
    sf.write(string("hello,world"));


}

我们在简单的实现一下RAII

#include <iostream>

using std::cout;
using std::endl;


template<typename T>
class RAII
{
private:
T* _data;

public:
    RAII(T* data)
    :_data(data)
    {
        cout<<"RAII(T* data)"<<endl;
    }

    ~RAII()
    {
        cout<<"~RAII()"<<endl;
        if(_data)
        {
            delete _data;
            _data=nullptr;
        }
    }

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

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

    T* get()
    {
        return _data;
    }
private:
    RAII(const RAII& rhs);
    RAII& operator=(const RAII& rhs);
};

class Point
{
private:
    int _a;
    int _b;
public:
    Point(int a=0,int b=0)
    :_a(a)
    ,_b(b)
    {
        cout<<"Point(int a=0,int b=0)"<<endl;
    }

    ~Point()
    {
        cout<<"~Point()"<<endl;
    }

    void print()
    {
        cout<<"( "<<_a<<" , "<<_b<<" )"<<endl;
    }
};

int main()
{
    //pt本身不是指针,但是具备指针的功能
    RAII<Point> pt(new Point(1,2));
    pt.operator->()->print();
    pt->print();
}

总结:这里pt本身不是指针,但他具备指针的功能,我们是用pt对象来托管new Point(1,2)这块堆空间,当pt对象的生命周期结束,自动调用析构函数,我们在将这块托管的堆空间释放。并且我们在RAII内设置一些方法可以访问这块托管的堆空间资源,这样我们就不用担心内存泄漏的问题,防止堆空间资源用完没有进行回收。

二.智能指针

在C++动态内存管理中,我们通过new在动态内存中为对象分配空间并返回一个指向该对象的指针,我们可以选择对对象进行初始化,通过delete接受一个动态对象的指针将该对象销毁。动态内存的使用很容易出问题,因为要确保在合适的时间释放内存是非常困难的。有时候我们会忘记释放内存,这时就会产生内存泄漏;有时在还有指针指向内存时我们就将内存释放了,这种情况会导致引用非法指针。因此为了更加容易和安全使用动态内存,标准库提供了智能指针类型来管理动态对象。

头文件<memory>

1.shared_ptr

shared_ptr既可以传左值也可以传右值,其中我们可以认为每个shared_ptr都有一个引用计数。无论何时我们拷贝一个shared_ptr引用计数都会递增。例如,当用一个shared_ptr去初始化另一个shared_ptr,或者将他作为参数传递给一个函数以及作为参数的返回值时,她所关联的计数器就会递增。当我们给shared_ptr赋予一个新值或是shared_ptr被销毁(例如一个局部的shared_ptr离开其作用域)时计数器就会递减,如果引用计数减少为0时,他就会自动释放自己所管理的类。

#include <iostream>
#include <memory>

using std::cout;
using std::endl;
using std::shared_ptr;
using std::string;
using std::make_shared;
using std::weak_ptr;

//与unique_ptr相反的是shared_ptr既可以传左值也可以传右值
void test()
{
    string str("helloworld");
    // shared_ptr<string> sp=std::make_shared<string>(new char[str.size()]());
    // *sp=str;

    //通过make_shared返回shared_ptr类型的指针,来对sp进行初始化
    shared_ptr<string> sp=std::make_shared<string>(str);

    cout<<"*sp = "<<*sp<<endl;
    cout<<"-------------------------------------"<<endl;

    shared_ptr<int> sp1(new int(10));
    cout<<"&*sp1 = "<<&*sp1<<endl;
    cout<<"*sp1 = "<<*sp1<<endl;
    cout<<"sp1.get() = "<<sp1.get()<<endl;
    cout<<"sp1.use_count() = "<<sp1.use_count()<<endl;
    cout<<"sp1.unique() = "<<sp1.unique()<<endl;

    
    cout<<endl;
    cout<<"赋值运算符函数"<<endl;
    shared_ptr<int> sp2(new int(20));
    cout<<"*sp2 = "<<*sp2<<endl;
    cout<<"sp2.get() = "<<sp2.get()<<endl;
    sp2=sp1;
     cout<<"&*sp2 = "<<&*sp2<<endl;
    cout<<"*sp2 = "<<*sp2<<endl;
    cout<<"sp2.get() = "<<sp2.get()<<endl;
    cout<<"sp2.use_count() = "<<sp2.use_count()<<endl;
    cout<<"sp2.unique() = "<<sp2.unique()<<endl;


    cout<<endl;
    shared_ptr<int> sp3=sp2;
    cout<<"&*sp3 = "<<&*sp3<<endl;
    cout<<"*sp3 = "<<*sp3<<endl;
    cout<<"sp3.get() = "<<sp3.get()<<endl;
    cout<<"sp3.use_count() = "<<sp3.use_count()<<endl;
    cout<<"sp3.unique() = "<<sp3.unique()<<endl;


}

int main()
{
    test();
    return 0;
}

2.unique_ptr

与shared_ptr不同的是,某个时刻只能有一个unique_ptr指向一个给定的对象。当unique_ptr被销毁时,它所指向的对象也被销毁。并且unique_ptr也没有类似make_shared的标准库函数返回一个unique_ptr。当我们定一个unique_ptr时,需要将其绑定到一个new返回的指针上。类似shared_ptr,初始化unique_ptr必须采用直接初始化方式。因为unique_ptr没有拷贝构造函数和赋值运算符函数,但是具有移动语义(有移动构造函数和移动赋值函数)并且可以作为容器元素,但是只能传递右值。

#include <iostream>
#include <memory>
#include <vector>

using std::vector;
using std::unique_ptr;
using std::cout;
using std::endl;

void test()
{
 unique_ptr<int> up(new int(2));


// unique_ptr<int> up1=up;//error 没有拷贝构造函数
// unique_ptr<int> up2;
// up2=up; //error没有赋值运算符

vector<unique_ptr<int>> vec_up;
//vec_up.push_back(up);   //error 还是会调用拷贝构造函数  unique_ptr(const unique_ptr&) = delete;

vec_up.push_back(std::move(up));
vec_up.push_back(unique_ptr<int>(new int(29)));

cout<<"vec_up[0] = "<<*vec_up[0]<<endl<<"vec_up[1] = "<<*vec_up[1]<<endl;
}


int main()
{
    test();
}

左右值相互转换的方法:

 需要右值的时候
 将左值转换为右值std::move()或构建临时对象Point(1,2)
 需要左值的时候
 可以使用构造函数创建对象,创建有名对象。Point pt(1,2)
 Point&& rhf=Point(1,2)   利用右值引用是左值的特性

3.weak_ptr

shared_ptr有一个缺陷就是循环引用的情况例如:

#include <iostream>
#include <memory>

using std::cout;
using std::endl;
using std::shared_ptr;
using std::string;
using std::make_shared;
using std::weak_ptr;

class Child;

class Parent
{
public:
    Parent()
    {
        cout<<"Parent()"<<endl;
    }
    ~Parent()
    {
        cout<<"~Parent()"<<endl;
    }
    shared_ptr<Child> pchild;
};

class Child
{
public:
    Child()
    {
        cout<<"Child()"<<endl;
    }
    ~Child()
    {
        cout<<"~Child()"<<endl;
    }

    shared_ptr<Parent> pparent;
};
//shared_ptr的问题循环引用的问题
void circle_test()
{
    shared_ptr<Parent> spParent(new Parent());
    shared_ptr<Child> spChild(new Child());

    cout<<"spParent.use_count() = "<<spParent.use_count()<<endl;
    cout<<"spChild.use_count() = "<<spChild.use_count()<<endl;

    //循环引用会导致内存泄露。解决方法weak_ptr和shared_ptr配合使用;其中weak_ptr不会使引用计数++
    spParent->pchild=spChild;   //sp=sp
    spChild->pparent=spParent;     
    cout<<"spParent.use_count() = "<<spParent.use_count()<<endl;
    cout<<"spChild.use_count() = "<<spChild.use_count()<<endl;
}
int main()
{
    circle_test();
}

可以看出来此时只有构造函数没有析构函数这就会造成内存泄漏。

创建完对象时指针引用如下图所示:

当进行析构时

就会造成引用计数不为零,并且指针相互只向。

class Child;

class Parent
{
public:
    Parent()
    {
        cout<<"Parent()"<<endl;
    }
    ~Parent()
    {
        cout<<"~Parent()"<<endl;
    }
    weak_ptr<Child> pchild;
};

class Child
{
public:
    Child()
    {
        cout<<"Child()"<<endl;
    }
    ~Child()
    {
        cout<<"~Child()"<<endl;
    }

    weak_ptr<Parent> pparent;
};

当将类的数据成员变成weak_ptr时

因为weak_ptr不会使引用计数递增

此时释放时引用计数都为零可以将堆空间进行释放。

weak_ptr的一些注意事项:

  1. weak_ptr没有成员访问运算符->和解引用运算符*。因此不能用weak_ptr指针去访问托管的数据。
  2. weak_ptr的初始化
    1. weak_ptr<Point> wp创建空对象
    2. weak_ptr<Point> wp(sp)用shared_ptr的指针类型对weak_ptr进行初始化
    3. weak_ptr<Point> wp=sp1
  3. 将weak_ptr提升一个shared_ptr,因为weak_ptr不能查看管理资源内容,转化为shared_ptr shared_ptr<Point> sp4=wp.lock();
  4. bool flag=wp.expired();如果use_count为0则flag为true,否则use_count不为0则flag为false 

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

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

相关文章

openGauss学习笔记-265 openGauss性能调优-TPCC性能调优测试指导-操作系统配置

文章目录 openGauss学习笔记-265 openGauss性能调优-TPCC性能调优测试指导-操作系统配置265.1安装openEuler操作系统265.2 修改操作系统内核PAGESIZE为64KB。265.3 关闭CPU中断的服务irqbalance openGauss学习笔记-265 openGauss性能调优-TPCC性能调优测试指导-操作系统配置 本…

oracle一次sql优化笔记

背景&#xff1a;两个百万级数据量表需要连接&#xff0c;加全索引的情况下速度仍不见改善&#xff0c;苦查一下午解决问题未遂。 解决&#xff1a;经大佬指点了解到oracle优化器提示&#xff0c;使用/* USE_HASH(table1 table2) */或者/* USE_MERGE(table1 table2) */来指导优…

正则表达式中 “$” 并不是表示 “字符串结束”

△△请给“Python猫”加星标 &#xff0c;以免错过文章推送 作者&#xff1a;Seth Larson 译者&#xff1a;豌豆花下猫Python猫 英文&#xff1a;Regex character “$” doesnt mean “end-of-string” 转载请保留作者及译者信息&#xff01; 这篇文章写一写我最近在用 Python …

数图可视化品类空间管理系统入编《零售门店数字化赋能专项报告(2024年)》

数图可视化品类空间管理系统荣幸入编中国连锁经营协会发布的 《零售门店数字化赋能专项报告&#xff08;2024年&#xff09;》&#xff0c;报告以零售门店为切入点&#xff0c;通过引入“5P”的技术框架及梳理业内配套最佳实践方案&#xff0c;理出一套科学的、完整的零售门店数…

掌握 JavaScript:如何正确声明和使用变量

在编程的世界里&#xff0c;数据是构建一切的基础。而在JavaScript中&#xff0c;变量就是存储数据的容器。它们就像是我们生活中的盒子&#xff0c;可以装下各种物品&#xff0c;让我们在需要的时候随时取用。 今天&#xff0c;就让我们一起揭开变量的神秘面纱&#xff0c;探…

在PostgreSQL中如何进行全文搜索,以及如何优化全文搜索性能?

文章目录 如何进行全文搜索1. 创建全文搜索向量2. 执行全文搜索查询 如何优化全文搜索性能1. 使用GIN索引2. 限制搜索范围3. 优化文本处理4. 使用并发搜索5. 监控和调整配置 在PostgreSQL中&#xff0c;全文搜索通常通过使用tsvector和tsquery类型&#xff0c;以及to_tsvector和…

Leetcode刷题-(26~35)-Java

算法是码农的基本功&#xff0c;也是各个大厂必考察的重点&#xff0c;让我们一起坚持写算法题吧。 遇事不决&#xff0c;可问春风&#xff0c;春风不语&#xff0c;即是本心。 我们在我们能力范围内&#xff0c;做好我们该做的事&#xff0c;然后相信一切都事最好的安排就可…

【NTN 卫星通信】NTN的SSB波束探讨

1 概述 SSB是同步广播信道&#xff0c;用于小区搜索&#xff0c;主系统消息的发送。NR协议中定义了多种SSB波束格式&#xff0c;简述如下。   小区搜索是终端获取与小区的时间和频率同步并检测小区的物理层小区ID的过程。   为了进行小区搜索&#xff0c;UE接收以下同步信号…

C# 将 TextBox 绑定为 KindEditor 富文本

目录 关于 KindEditor 绑定设计 部署 KindEditor 实现代码 小结 关于 KindEditor KindEditor 基于JavaScript 编写&#xff0c;可以与众多WEB应用程序结合。KindEditor 依靠出色的用户体验和领先的技术提供富文本编辑功能&#xff0c;是一款非常受欢迎的HTML在线编辑器。…

Modelsim与Verilog入门

0.什么是Modelsim&#xff1f; Modelsim是一个支持多语言的仿真环境&#xff0c;比如我知道的Verilog和VHDL语言都可以在里边使用&#xff0c;这俩都是硬件描述语言&#xff1b; 即就是个软件&#xff0c;你可以用Verilog语言来写代码&#xff0c;然后编译&#xff0c;仿真出…

Spring AI ETL 流水线

先纠正 Spring AI 使用本地 Ollama Embeddings 中的一个错误&#xff0c;当启动 Ollama 之后&#xff0c;Windows会有托盘图标&#xff0c;此时已经启动了 Ollama 的服务&#xff0c;访问 Embedding 时不需要运行 ollama run gemma &#xff0c;只有访问 chat 时才需要启动一个…

轨迹跟踪与控制篇——Pure Pursuit纯跟踪算法

介绍 纯跟踪控制算法(Pure Pursuit)是一种典型的横向控制方法&#xff0c;最早由R.Wallace在1985年提出&#xff0c;该方法对参考轨迹的鲁棒性较好。 该算法的思想&#xff1a;基于当前车辆后轮中心位置&#xff0c;在参考路径上向 与 自车后轴中心距离ld(自定义)的位置处 匹配…

牛客-小乐乐与欧几里得

目录 题目 描述 输入描述&#xff1a; 输出描述&#xff1a; 示例1 示例2 解题 题目 描述 小乐乐最近在课上学习了如何求两个正整数的最大公约数与最小公倍数&#xff0c;但是他竟然不会求两个正整数的最大公约数与最小公倍数之和&#xff0c;请你帮助他解决这个问题。 …

工业现场ModbusTCP转EtherNETIP网关引领生物现场领新浪潮

生物质发生器是一种能够产生、培养生物的设备。客户现场需要将生物发生器连接到罗克韦尔系统&#xff0c;但是二者协议无法直接通讯&#xff0c;需要通过开疆智能ModbusTCP转Ethernet/IP网关将两者进行通讯连接&#xff0c;生物质发生器以其独特的工作原理和优势&#xff0c;使…

强固型国产化工业电脑,在电子看板行业应用,机器视觉在汽车产线行业应用

电子看板行业应用 智能电子看板的核心是通过实现工厂的全面可视化、自动化管理&#xff0c;最终达到提高效率、降低成本及提高产品质量的目标。电子看板硬件主要有两部分组成&#xff1a;微型工业计算机&#xff0c;显示终端&#xff08;平板电视、LCD&#xff09; 方案需求 …

【c基础】文件操作

1.fopen和fclose函数 函数原型 FILE *fopen(const char *path, const char *mode); 参数解释&#xff1a; 返回值&#xff1a;fopen打开成功&#xff0c;则返回有效file的有效地址&#xff0c;失败返回NULL。path是文件路径&#xff0c;可以相对路径&#xff0c;可以绝对路径…

《黑羊效应》一群好人欺负一个好人,其他好人却坐视不管的诡谲现象 - 三余书屋 3ysw.net

黑羊效应&#xff1a;一群好人欺负一个好人&#xff0c;其他好人却坐视不管的诡谲现象 大家好&#xff0c;今天我们要解读的书是《黑羊效应》。黑羊效应是一种心理陷阱&#xff0c;指的是一群好人欺负一个好人&#xff0c;而其他好人却坐视不理。我们每个人或多或少都目睹过或…

理解JMM

JMM 对volatile的理解 volatile 是java虚拟机提供轻量级的同步机制 1、保证可见性 2、不保证原子性 3、禁止指令重排 那么可见性与JMM相关 什么是JMM Java内存模型&#xff0c;不存在的东西&#xff0c;是一个概念&#xff0c;是一个约定 线程加锁前&#xff0c;必须读取…

存储过程的使用(二)

目录 带 OUT 参数的存储过程 输入一个编号&#xff0c;查询数据表 emp中是否有这个编号&#xff0c;如果有返回对应员工姓名&#xff0c;如果没有&#xff0c;则提示没有对应员工 使用 EXEC 命令或者 PRINT执行含有 OUT参数的存储过程 使用 PL/SQL 块编辑程序调用含有 OUT …

centos修改启动项加载不同内核

一.背景&#xff1a; 虚拟机中有时需要编译好几个内核版本&#xff0c;make install后系统存在几个内核版本。需要再哪个内核上开发调试就启动特定的内核版本。这就需要修改启动时的内核版本&#xff0c;再物理机或虚拟机启动时可以上下键选择。但有时是docket云环境中或远程时…