C++智能指针(二十)

一.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 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

OpenHarmony其他工具类—lua

简介 Lua是一种功能强大、高效、轻量级、可嵌入的脚本语言。 支持过程编程、面向对象编程、函数编程、数据驱动编程和数据描述。 下载安装 直接在OpenHarmony-SIG仓中搜索lua并下载。 使用说明 以OpenHarmony 3.1 Beta的rk3568版本为例 将下载的lua库代码存在以下路径&#…

C# 将 TextBox 绑定为 KindEditor 富文本

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

【FreeRTOS】使用CubeMX快速移植FreeRTOS工程到蓝桥杯开发板(STM32G431RBT6)

使用CubeMX快速创建FreeRTOS工程到蓝桥杯开发板&#xff08;STM32G431RBT6&#xff09; CubeMX配置CubeMX基础工程的配置☆FreeRTOS相关配置FreeRTOS配置选项卡的解释 软件工程架构与程序设计小综合&#xff1a;☆任务的创建删除、挂起与恢复设计cubexMX配置创建任务软件程序设…

高频前端面试题汇总之JavaScript篇(上)

一、数据类型 1. JavaScript有哪些数据类型&#xff0c;它们的区别&#xff1f; JavaScript共有八种数据类型&#xff0c;分别是 Undefined、Null、Boolean、Number、String、Object、Symbol、BigInt。 其中 Symbol 和 BigInt 是ES6 中新增的数据类型&#xff1a; Symbol 代…

关于 Windows10 计算机丢失 MSVCP120.dll 的解决方法

今天学长跟平时一样打开电脑开始发布文章需要用到Adobe Photoshop CC 2018的时候居然给我来个Photoshop.exe-系统错误、无法启动此程序&#xff0c;因为计算机中丢失MSVCP120.dll 尝试重新安装该程序以解决此问题&#xff0c;安装上面的说明重新安装了我的Photoshop CC 打开还是…

关于CAS

什么是CAS: CAS:Compare And Swap&#xff0c;比较且交换。 CAS中有三个参数&#xff1a;1.内存中原数据的值V 2.预期值A 3.修改后的数据B Compare&#xff1a;V与A会先比较是否一样 Swap&#xff1a;如果V与A一致&#xff0c;那么就将B写入V 返回操作是否成功 伪代码&…

椋鸟数据结构笔记#10:排序·中

文章目录 四、归并排序时间复杂度实现递归实现非递归实现 测试稳定性 五、非比较排序5.1 计数排序时间复杂度实现测试局限性 5.2 桶排序时间复杂度实现测试 5.3 基数排序时间复杂度实现测试局限性 萌新的学习笔记&#xff0c;写错了恳请斧正。 四、归并排序 归并排序是一种非常…

微服务使用SockJs+Stomp实现Websocket 前后端实例 | Vuex形式断开重连、跨域等等问题踩坑(一)

大家好&#xff0c;我是程序员大猩猩。 之前几篇文章&#xff0c;我们讲了Spring Cloud Gateway的轻量级实现&#xff0c;Nginx的配置概念与实现&#xff0c;如以下往期文章。 轻量级的Spring Cloud Gateway实践&#xff0c;实现api和websocket转发轻松实现Nginx的HTTP与WebS…

新产品成功的七大关键要素:理论解析与案例探讨

在激烈的市场竞争中&#xff0c;新产品的成功推出不仅关乎企业的生死存亡&#xff0c;更是企业持续发展的核心动力。那么&#xff0c;新产品如何能够脱颖而出&#xff0c;赢得市场的青睐呢&#xff1f;本文将深入探讨新产品成功的七大关键要素&#xff0c;并结合实际案例进行解…

中颖51芯片学习8. ADC模数转换

中颖51芯片学习8. ADC模数转换 一、ADC工作原理简介1. 概念2. ADC实现方式3. 基准电压 二、中颖芯片ADC功能介绍1. 中颖芯片ADC特性2. ADC触发源&#xff08;1&#xff09;**软件触发**&#xff08;2&#xff09;**TIMER4定时器触发**&#xff08;3&#xff09;**外部中断2触发…

洛谷P1057 [NOIP2008 普及组] 传球游戏

#include<iostream> using namespace std; int n;// n个人传球游戏 默认开始球在编号为1的位置 int m;// 传递m次球 int main(){cin>>n>>m;// 动态转方程&#xff1a;// 球传递到编号为k人的手中// 种类总数 传递到k-1编号种类总数 传递到k1编号种类总数//…

如何查看微信公众号发布文章的主图,如何看微信文章的主图,怎么才能拿到主图

如何查看&#xff0c;微信公众号发布文章的主图&#xff0c;如何看微信文章的主图 起因是这样的&#xff0c;当我看到一篇文章的时候&#xff0c;他的主图很漂亮&#xff0c;但是&#xff0c;正文里没有&#xff0c;而我又想看到&#xff0c;并且使用这张图片&#xff0c;该怎么…

代码随想录训练营Day 27|Python|Leetcode|122.买卖股票的最佳时机II ● 55. 跳跃游戏 ● 45.跳跃游戏II

122.买卖股票的最佳时机II 给你一个整数数组 prices &#xff0c;其中 prices[i] 表示某支股票第 i 天的价格。 在每一天&#xff0c;你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买&#xff0c;然后在 同一天 出售。 返回 你能获…

同旺科技 USB TO SPI / I2C适配器读写24LC256--页写

所需设备&#xff1a; 1、USB 转 SPI I2C 适配器&#xff1b;内附链接 2、24LC256芯片 适应于同旺科技 USB TO SPI / I2C适配器升级版、专业版&#xff1b; 从00地址开始写入64个字节&#xff0c;然后再将64个字节读回&#xff1b; 页写时序&#xff1a; 读时序&#xff1a…

easyx库的介绍

前言 如果想要摆脱黑窗口的限制那么easyx图形库是一个好的选择 easyx的初认识 easyx是针对c的图形库&#xff0c;可以帮助c/c上手图形和游戏编程 所以要用easyx必须要用.cpp的后缀 1 easyx的原理 window的图形编程&#xff0c;最终都由window的底层API来实现 2 easyx的颜色 …

【Java笔记】第4章:深入学习循环结构

前言1. 循环的理解2. while循环3. do...while循环4. for循环5. 循环的控制语句6. 循环的嵌套结语 ↓ 上期回顾: 【Java笔记】第3章&#xff1a;深入学习分支结构 个人主页&#xff1a;C_GUIQU 归属专栏&#xff1a;【Java学习】 ↑ 前言 各位小伙伴大家好&#xff01;上期小编…

Mac下删除旧版本.net sdk

参照微软官网给的方法,Releases dotnet/cli-lab (github.com) 好像不能直接的解决问题,我做一下补充,希望对需要删除旧版本sdk的小伙伴们有所帮助 1:下载工具包 Releases dotnet/cli-lab (github.com) 2:打开终端,cd切换到该文件的制定目录 3:然后按照提示一步步执行…

2024上海国际半导体制造设备材料与核心部件展览会

2024上海国际半导体制造设备材料与核心部件展览会 2024 Shanghai International Semiconductor Manufacturing Equipment Materials and Core Components Exhibition 时间&#xff1a;2024年11月18日-20日 地点&#xff1a;上海新国际博览中心 详询主办方陆先生 I38&#…

【干货精品分享】Elasticsearch 6.7 Should 子语句的失效

在ES 使用多条件 查询&#xff0c;并且是多个条件只需要满足部分条件的时候&#xff0c;我们通常会使用到ES的should查询 GET /trademark_query_index/_search {"query":{"bool" : {"must":[{"match" : {"origin": {"…

PACS系统源码 新一代的医学图像管理系统 pacs 云影像,PACS云胶片,PACS影像工作站系统源码

PACS系统源码 新一代的医学图像管理系统 pacs 云影像,PACS云胶片,PACS影像工作站系统源码 三甲医院医学影像PACS系统源码&#xff0c;集成三维影像后处理功能&#xff0c;包括三维多平面重建、三维容积重建、三维表面重建、三维虚拟内窥镜、最大/小密度投影、心脏动脉钙化分…