智能指针(二) shared_ptr 注意点

news2024/11/15 19:59:25

智能指针(二) shared_ptr 注意点

1 不存在 int * 到 shared_ptr 的隐式类型转换

void proc(shared_ptr<int> ptr)
{
    cout << "ptr.use_count()=" << ptr.use_count() << endl;
    cout << "调用成功" << endl;
    return;
}
  int *p = new int(100);

    // todo  1 不存在 int * 到 shared_ptr<int> 的隐式类型转换
    //  proc(p);

2切勿传递临时引用作为函数参数


void proc(shared_ptr<int> ptr)
{
    cout << "ptr.use_count()=" << ptr.use_count() << endl;
    cout << "调用成功" << endl;
    return;
}
  int *p = new int(100);
proc(std::shared_ptr<int>(p)); // 参数是临时的 shared_ptr,用裸指针显示构造
    //  传递到 proc 函数中的引用计数为 1 ,离开函数体后,引用计数为 0 , 临时构造的shared_ptr指针所指向的内存(也就是p 指向的内存) 被 回收
   *p =25; //此时操作p将是违法的(已置空)
  • 把一个普通裸指针绑定到一个shared_ptr 上之后, 内存管理的责任就交给这个shared_ptr了,这个时候就不应该用裸指针来访问shared_ptr所指向的内存了

修正的做法是

 shared_ptr<int> pi(p);
    proc(pi);
    *pi = 45; // 操作安全
    // 传递到 proc 函数中的引用计数为 2 ,离开函数体后,引用计数为 1 ,pi 指向的对象 不会被回收。

3严禁 用裸指针初始化多个 shared_ptr

  • 多个shared_ptr 并不会增加引用计数,内存管理将不会贯通(初始化 shared_ptr,但二者并不指向同一个控制块,引用计数不会累计),
 int *p = new int(100);
    void func()
    {
        shared_ptr<int> p1(p);
        cout << "p1.use_count()" << p1.use_count() << endl;
        shared_ptr<int> p2(p);
        cout << "p1.use_count()" << p1.use_count() << endl;
        // 当 p1 p2 的生命结束时, 指向的对象将被 释放两次(因为p1和p2引用计数均为1,而且p2 是释放同一块已经释放内存的空间)

        // 应用下面的方式代替
        /*
        shared_ptr<int> p1(new int); // 用匿名裸指针 初始化
        shared_ptr<int> p2(p1); //用p1 初始化 其他 shared_ptr


        */
    }

4谨慎 使用 get 返回的裸指针

  • 注意 :get() 返回的裸指针不能delete,否则会异常
 void func()
    {
        cout << "detail2::func()" << endl;
        shared_ptr<int> pi(new int);
        int *p = pi.get();
        delete p;// error
    }
  • 不能将智能指针 绑定到get 返回的指针上
void func2()
    {

        // ?. 类似于把裸指针赋值给了两个shared_ptr
        shared_ptr<int> pi(new int);
        cout << "pi.use_count()" << pi.use_count() << endl;
        int *p = pi.get();
        shared_ptr<int> pi2(p);
        cout << "pi.use_count()" << pi.use_count() << endl;
        cout << "pi2.use_count()" << pi2.use_count() << endl;
    }

5 不要返回类对象指针

  • 使用类对象this初始化另一个对象, 引用计数将不会增加
 class TC
    {
    public:
        shared_ptr<TC> get_self_this()
        {
            return shared_ptr<TC>(this);
        }
    };
     void func()
    {
        cout << "detail::func()" << endl;
        shared_ptr<TC> p1(new TC);
        cout << "p1.use_count()" << p1.use_count() << endl;
        shared_ptr<TC> p2 = p1->get_self_this();

        cout << "p1.use_count()" << p1.use_count() << endl;
    }
  • 使用 shared_from_this 代替
class TC2 : public enable_shared_from_this<TC2>
    {
    public:
        shared_ptr<TC2> get_self_enable_shared()
        {
            // /这个就是enable_shared_from_this类中的方法,要通过此方法返回智能指针
            return shared_from_this();
        }
    };
    
      void func2()
    {
        cout << "detail2::func2()" << endl;
        shared_ptr<TC2> p1(new TC2);
        cout << "p1.use_count()" << p1.use_count() << endl;
        shared_ptr<TC2> p2 = p1->get_self_enable_shared();

        cout << "p1.use_count()" << p1.use_count() << endl;
    }

6 循环引用问题

 class CB;
    class CA
    {
    public:
        CA() { cout << "CA() called! " << endl; }
        ~CA() { cout << "~CA() called! " << endl; }
        void set_ptr(shared_ptr<CB> &ptr) { m_ptr_b = ptr; }
        void b_use_count() { cout << "b use count : " << m_ptr_b.use_count() << endl; }
        void show() { cout << "this is class CA!" << endl; }

    private:
        shared_ptr<CB> m_ptr_b;
    };

    class CB
    {
    public:
        CB() { cout << "CB() called! " << endl; }
        ~CB() { cout << "~CB() called! " << endl; }
        void set_ptr(shared_ptr<CA> &ptr) { m_ptr_a = ptr; }
        void a_use_count() { cout << "a use count : " << m_ptr_a.use_count() << endl; }
        void show() { cout << "this is class CB!" << endl; }

    private:
        shared_ptr<CA> m_ptr_a;
    };

    void test_refer_to_each_other()
    {
        shared_ptr<CA> ptr_a(new CA());
        shared_ptr<CB> ptr_b(new CB());

        cout << "a use count : " << ptr_a.use_count() << endl;
        cout << "b use count : " << ptr_b.use_count() << endl;

        ptr_a->set_ptr(ptr_b);
        ptr_b->set_ptr(ptr_a);

        cout << "a use count : " << ptr_a.use_count() << endl;
        cout << "b use count : " << ptr_b.use_count() << endl;
    }
    int main()
    {
    test_refer_to_each_other();
    }
CA() called!
CB() called!
a use count : 1
b use count : 1
a use count : 2
b use count : 2
  • 通过结果可以看到,最后CA和CB的对象并没有被析构,其中的引用效果如下图所示,起初定义完ptr_a和ptr_b时,只有 ptr_a 指向new_CA() 和ptr_b 指向 new CB()两条引用,
  • 然后调用函数set_ptr后又增加了m_ptr_a 指向 new CA() 和 m_ptr_b指向 new CB 两条引用,当test_refer_to_each_other这个函数返回时,对象ptr_a和ptr_b被销毁,
  • 也就是【ptr_a 指向new_CA() 和ptr_b 指向 new CB()两条引用 】会被断开,但是依然m_ptr_a 指向 new CA() 和 m_ptr_b指向 new CB 两条引用存在,
  • 每一个的引用计数都不为0,结果就导致其指向的内部对象无法析构(不能调用析构函数),造成内存泄漏。

wk_shared_ptr

解决这种状况的办法就是将两个类中的一个成员变量改为weak_ptr对象,因为weak_ptr不会增加引用计数,使得引用形不成环,最后就可以正常的释放内部的对象,不会造成内存泄漏,比如将CB中的成员变量改为weak_ptr对象,代码如下:

  class CB;
    class CA
    {
    public:
        CA() { cout << "CA() called! " << endl; }
        ~CA() { cout << "~CA() called! " << endl; }
        void set_ptr(shared_ptr<CB> &ptr) { m_ptr_b = ptr; }
        void b_use_count() { cout << "b use count : " << m_ptr_b.use_count() << endl; }
        void show() { cout << "this is class CA!" << endl; }

    private:
        shared_ptr<CB> m_ptr_b;
    };
    /*
    todo 解决这种状况的办法就是将两个类中的一个成员变量改为weak_ptr对象,因为weak_ptr不会增加引用计数,使得引用形不成环,
    todo 最后就可以正常的释放内部的对象,不会造成内存泄漏,比如将CB中的成员变量改为weak_ptr对象,代码如下:
    */
    class CB
    {
    public:
        CB() { cout << "CB() called! " << endl; }
        ~CB() { cout << "~CB() called! " << endl; }
        void set_ptr(shared_ptr<CA> &ptr) { m_ptr_a = ptr; }
        void a_use_count() { cout << "a use count : " << m_ptr_a.use_count() << endl; }
        void show() { cout << "this is class CB!" << endl; }

    private:
        weak_ptr<CA> m_ptr_a;
    };

    void test_refer_to_each_other()
    {
        shared_ptr<CA> ptr_a(new CA());
        shared_ptr<CB> ptr_b(new CB());

        cout << "a use count : " << ptr_a.use_count() << endl;
        cout << "b use count : " << ptr_b.use_count() << endl;

        ptr_a->set_ptr(ptr_b);
        ptr_b->set_ptr(ptr_a);

        cout << "a use count : " << ptr_a.use_count() << endl;
        cout << "b use count : " << ptr_b.use_count() << endl;
    }

测试结果如下:

CA() called!
CB() called!
a use count : 1
b use count : 1
a use count : 1
b use count : 2
~CA() called!
~CB() called!
  • 通过这次结果可以看到,CA和CB的对象都被正常的析构了,*
  • 但是不同的是m_ptr_a指向new CA()这条引用是通过weak_ptr建立的,并不会增加引用计数,也就是说CA的对象只有一个引用计数,*
  • t而CB的对象只有2个引用计数,当test_refer_to_each_other这个函数返回时,对象ptr_a和ptr_b被销毁,
  • 也就是 ptr_a 指向new_CA() 和ptr_b 指向 new CB()两条引用 会被断开,此时CA对象的引用计数会减为0,对象被销毁,*
  • *其内部的m_ptr_b成员变量也会被析构,导致CB对象的引用计数会减为0,对象被销毁,进而解决了引用成环的问题。

wk_weak_ptr

7移动语义

  • 使用 std::move 可以将智能指针拥有权 转交给另一个智能指针,而不引起引用计数的增加
 void func()
    {
        cout << "detail6::func" << endl;
        shared_ptr<int> si = make_shared<int>(100);
        cout << "si.use_count()" << si.use_count() << endl;
        auto si2 = si;
        cout << "si.use_count()" << si.use_count() << endl;
        cout << "si2.use_count()" << si2.use_count() << endl;
    }
    void func2()
    {
        cout << "detail6::func2" << endl;
        shared_ptr<int> si = make_shared<int>(100);
        cout << "si.use_count()" << si.use_count() << endl;
        auto si2 = std::move(si);

        cout << "si.use_count()" << si.use_count() << endl;
        cout << "si2.use_count()" << si2.use_count() << endl;
    }

<< “detail6::func2” << endl;
shared_ptr si = make_shared(100);
cout << “si.use_count()” << si.use_count() << endl;
auto si2 = std::move(si);

    cout << "si.use_count()" << si.use_count() << endl;
    cout << "si2.use_count()" << si2.use_count() << endl;
}

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

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

相关文章

独立产品灵感周刊 DecoHack #043 - 互联网从业者的灵感数据库

本周刊记录有趣好玩的独立产品设计开发相关内容&#xff0c;每周发布&#xff0c;往期内容同样精彩&#xff0c;感兴趣的伙伴可以点击订阅我的周刊。为保证每期都能收到&#xff0c;建议邮件订阅。欢迎通过 Twitter 私信推荐或投稿。很完美的断更了2期&#xff0c;有一期是因为…

RFID技术和NFC技术的原理及区别,你都了解吗?

物联网是信息技术发展的重要推动力&#xff0c;推动了农业、工业、制造业、服务业等多个行业的发展&#xff0c;物联网主要由三个关键技术组成&#xff0c;即连接、物体标识和数据传输&#xff0c;人们把RFID技术作为物体标识的代表&#xff0c;随着技术的进步起源于RFID技术的…

谷粒商城-基础篇-Day07-品牌分类关联与级联更新

将品牌分类和品牌名称的关系放在pms_category_brand_relation表中 获取该列表品牌所有的关联信息 /*** 列表*/GetMapping("/catelog/list")public R list(RequestParam("brandId") Long brandId){List<CategoryBrandRelationEntity> datacategoryBra…

Java日期时间类

Java日期时间类Datenew Date()**获取当前系统时间**通过**指定毫秒数得到时间**format**指定日期格式**SimpleDateFormat的模式字母&#xff1a;parse()可以把**格式化的String转成对应Date**Calendar&#xff08;日历&#xff09;创建日期类对象获取日历对象的某个日历字段第三…

【Linux】五、Linux 进程控制(总)|进程创建|进程终止|进程等待进程程序替换|模拟shell

目录 一、进程创建 1.1 再谈 fork 函数 1.2 fork 函数返回值问题 1.2 写时拷贝 1.3 fork 常规用法 1.4 fork调用失败的原因 二、进程终止 2.1 进程退出码 2.2 进程退出场景 2.3 进程如何退出 三、进程等待 3.1 进程等待必要性 3.2 进程等待的方法 3.2.1 通过 wai…

【二进制安全面试题】linux篇:保护机制、函数调用约定

前言 上来先道歉&#xff0c;对不起(&#xff1e;人&#xff1c;&#xff1b;)对不起&#xff0c;博客鸽了好久。私下有好多朋友问我毕业工作的事情&#xff0c;毕竟搞二进制最重要的是要有热情&#xff01;我能做的也是有限&#xff0c;每个人的学习方式不完全相同&#xff0c…

Http4s 存在输入验证不当漏洞(CVE-2023-22465)

漏洞描述 http4s 是一个用于处理 HTTP 服务的 Scala 接口。 http4s 的受影响版本延迟加载模型化标头&#xff08;modeled headers&#xff09;&#xff0c;用于处理规范化标头的请求&#xff08;如&#xff1a;Option[Header] req.headers.get(“User-Agent”.ci)&#xff0…

C语言进阶——字符串函数(一)

目录 一. strlen 二. strcpy 三. strcat 四. strcmp 五. strncpy 六. strncat 七. strncmp 八. strstr 九. strtok 一. strlen 字符串以 \0 作为结束标志&#xff0c;strlen函数返回的是在字符串中 \0 前面出现的字符个数&#xff08;不包 含 \0 …

陪诊软件开发,陪诊服务具备哪些好处,前景如何

在当下互联网快速发展的时代&#xff0c;我们要首先明确&#xff0c;一个行业的发展最重要的是什么&#xff0c;什么才能促进这个行业的前进。当然是用户的数量&#xff0c;**而我们的陪诊服务&#xff0c;潜在的用户数量是巨大的。因为自己独立不便就医的人群&#xff0c;都可…

maven导入第三方jar包,出现找不到类

我们开发时&#xff0c;会用到第三代第三方的jar包&#xff0c;私服上没有&#xff0c;只能导入使用。 导入步骤&#xff1a; 1、在项目根目录建文件夹lib&#xff0c;降jar包复制过去。 在pom.xml中引入jar包&#xff0c;如引入bcprov-jdk15on-1.59.jar <dependency>&…

c++ - 第21节 - 智能指针

1.为什么需要智能指针 分析一下下面这段程序有没有什么内存方面的问题&#xff1f;前面在异常的博客中&#xff0c;我们分析了下图一的代码Func函数中如果div()函数抛异常则程序会直接跳到主函数的catch捕获程序部分&#xff0c;然后接着主函数catch捕获程序部分往后执行代码&a…

【IOS的safari浏览器】uniapp的H5项目 safari<添加到主屏幕>功能的实现(多页面、单页面)

uniapp的H5项目safari <添加到主屏幕>功能的实现ios添加到主屏幕的需求具体效果实现前提完整的HTML页面如何判断应用是从主屏幕打开还是从浏览器打开特殊情况ios添加到主屏幕的需求 添加到主屏幕——这个功能属于ios的safari浏览器的特性之一&#xff0c;他可以让我们的…

Java环境安装、替换jdk后java编译javac无反应,但java和java -version可以成功:实操解决方案

这里写自定义目录标题问题背景方案一方案二方案三问题背景 最近换了新电脑&#xff0c;安装java环境&#xff0c;一次性下载了3个jdk版本&#xff0c;在配置后返现 cmd命令行下javac编译java文件不成功&#xff0c;但是输入java和java -version没问题 在CSDN看了许多解决方案…

linux安装go

下载地址 https://studygolang.com/dl?id2&id15&id0&id8&adinfo678baidu&adinfo678baidu%3Epage%3E go语言中文网 解压 tar -xvf go1.19.4.linux-amd64.tar.gz 解压之后在 root目录下面 有个 go的文件夹 vim ~/.bashrc 配置环境变量 export GOROOT/roo…

【Dash搭建可视化网站】项目10:疫情数据可视化大屏制作步骤详解

疫情数据可视化大屏制作步骤详解1 项目效果图2 项目架构3 文件介绍和功能完善3.1 assets文件夹介绍3.2 app.py和index.py文件完善3.3 header.py文件完善3.4 cards.py文件完善3.5 api.py和api.ipynb文件完善3.5.1 数据获取3.5.2 数据处理3.5.3 接口数据导入header.py和cards.py文…

SpringMVC基本使用

SpringMVC基本使用1、回顾MVC1.1、什么是MVC1.2、Model1时代1.3、Model2时代1.4、回顾Servlet2、什么是SpringMVC2.1、概述2.2、中心控制器2.3、SpringMVC执行原理3、HelloSpring3.1、配置版3.2、注解版3.3、小结4、Controller 及 RestFul4.1、控制器Controller4.2、实现Contro…

【笔记:第一课】学习开发一个RISC-V上的操作系统 - 汪辰 - 2021春

文章目录前言来源正文小结前言 创作开始时间&#xff1a;2023年1月9日20:02:19 如题&#xff0c;学习一下RISC-V。 来源 https://www.bilibili.com/video/BV1Q5411w7z5/ 正文 打好基础&#xff01;好好学习 本课程目的&#xff1a; 了解 RISC-V 的相关知识学会查看RISC-…

week10

T1 Einstein学画画 题目描述 Einstein 学起了画画。 此人比较懒~~&#xff0c;他希望用最少的笔画画出一张画…… 给定一个无向图&#xff0c;包含 nnn 个顶点&#xff08;编号 1∼n1 \sim n1∼n&#xff09;&#xff0c;mmm 条边&#xff0c;求最少用多少笔可以画出图中所…

解决RuntimeError: CUDA error: out of memory

注意&#xff1a;报错内容只有这一行&#xff0c;RuntimeError: CUDA error: out of memory&#xff0c;没有后面的内存分析。 因为报错的时候忘记截图了&#xff0c;修改好了才来记录的。这里引用别的博主的图片。图片来源 1&#xff1a;刚开始我怀疑是batchsize设的太大了&a…

vue01-基础

一、vue简介 1.1 描述 一套用于构建用户界面的渐进式JavaScript框架 构建用户界面&#xff1a;把数据处理成界面 渐进式&#xff1a;可以从简单应用引入的轻量小巧核心库&#xff0c;扩展至各式vue插件 1.2 特点 1.组件化模式&#xff0c;提高代码复用率且便于维护&#…