码农知识堂 - 1000bd
  •   Python
  •   PHP
  •   JS/TS
  •   JAVA
  •   C/C++
  •   C#
  •   GO
  •   Kotlin
  •   Swift
  • c++智能指针


    1、std::auto_ptr

            std::auto_ptr真正容易让人误用的地方是其不常用的复制语义,即当复制一个std::auto_ptr对象时(拷贝复制或operator =复制),原对象所持有的堆内存对象也会转移给复制出来的对象。示例代码如下:

    1. //测试拷贝构造函数
    2. std::auto_ptr<int> sp1(new int(8));
    3. std::auto_ptr<int> sp2(sp1);
    4. if (sp1.get() != NULL)
    5. {
    6. std::cout << " sp1 is not empty" << std::endl;
    7. }
    8. else {
    9. std::cout << " sp1 is empty" << std::endl;
    10. }
    11. if (sp2.get() != NULL)
    12. {
    13. std::cout << " sp2 is not empty" << std::endl;
    14. }
    15. else {
    16. std::cout << " sp2 is empty" << std::endl;
    17. }
    18. //测试赋值构造
    19. std::auto_ptr<int> sp3(new int(8));
    20. std::auto_ptr<int> sp4;
    21. sp4 = sp3;
    22. if (sp3.get() != NULL)
    23. {
    24. std::cout << " sp3 is not empty" << std::endl;
    25. }
    26. else {
    27. std::cout << " sp3 is empty" << std::endl;
    28. }
    29. if (sp4.get() != NULL)
    30. {
    31. std::cout << " sp4 is not empty" << std::endl;
    32. }
    33. else {
    34. std::cout << " sp4 is empty" << std::endl;
    35. }

    上述代码中分别利用拷贝构造(sp1=>sp2)和赋值构造(sp3=>sp4)来创建新的std::auto_ptr对象,因此sp1 持有的堆对象被转移给sp2,sp3持有的堆对象被转移给sp4。因此输出为:

    1. sp1 is empty
    2. sp2 is not empty
    3. sp3 is empty
    4. sp4 is not empty

    例如:

    std::vector> myvectors;

    当用算法对容器操作的时候(如最常见的容器元素遍历),很难避免不对容器中的元素实现赋值传递,这样便会使容器中多个元素被置为空指针,这不是我们希望看到的,可能会造成一些意想不到的错误。

    2、std::unique_ptr

    std::unique_ptr对其持有的堆内存具有唯一拥有权,也就是说引用计数永远是1,std::unique_ptr对象销毁时会释放其持有的堆内存。可以使用以下方式初始化一个std::unique_ptr对象:

    1. //1
    2. std::unique_ptr<int> sp1(new int(123));
    3. //2
    4. std::unique_ptr<int> sp2;
    5. sp2.reset(new int(123));
    6. //3
    7. std::unique_ptr<int> sp3 = std::make_unique<int>(123);

    应该尽量使用初始化方式3的方式去创建一个std::unique_ptr而不是方式1和2,因为形式3更安全。

    std::unique_ptr禁止复制语义,为了达到这个效果,std::unique_ptr类的拷贝构造函数和赋值运算符(operator =)被标记为 =delete。

    1. template <class T>
    2. class unique_ptr
    3. {
    4. //省略其他代码...
    5. //拷贝构造函数和赋值运算符被标记为delete
    6. unique_ptr(const unique_ptr&) = delete;
    7. unique_ptr& operator=(const unique_ptr&) = delete;
    8. };

    因此,下列代码是无法通过编译的:

    1. std::unique_ptr<int> sp1(new int(123));
    2. //报错
    3. std::unique_ptr<int> sp2(sp1);
    4. std::unique_ptr<int> sp3;
    5. //报错
    6. sp3 = sp2;

    禁止复制语义也存在特例,即可以通过一个函数返回一个std::unique_ptr。

    既然std::unique_ptr不能复制,那么如何将一个std::unique_ptr对象持有的堆内存转移给另外一个呢?答案是使用移动构造,示例代码如下:

    1. std::unique_ptr<int> sp1(std::make_unique<int>(123));
    2. std::unique_ptr<int> sp2(std::move(sp1));
    3. std::unique_ptr<int> sp3;
    4. sp3 = std::move(sp2);

    以上代码利用std::move将sp1持有的堆内存(值为123)转移给sp2,再把sp2转移给sp3。最后,sp1和sp2不再持有堆内存的引用,变成一个空的智能指针对象。并不是所有的对象的std::move操作都有意义,只有实现了移动构造函数(Move Constructor)或移动赋值运算符(operator =)的类才行,而std::unique_ptr正好实现了这二者。

    3、std::shared_ptr

            std::unique_ptr对其持有的资源具有独占性,而std::shared_ptr持有的资源可以在多个std::shared_ptr之间共享,每多一个std::shared_ptr对资源的引用,资源引用计数将增加1,每一个指向该资源的std::shared_ptr对象析构时,资源引用计数减1,最后一个std::shared_ptr对象析构时,发现资源计数为0,将释放其持有的资源。多个线程之间,递增和减少资源的引用计数是安全的。(注意:这不意味着多个线程同时操作std::shared_ptr引用的对象是安全的)。std::shared_ptr提供了一个use_count()方法来获取当前持有资源的引用计数。除了上面描述的,std::shared_ptr用法和std::unique_ptr基本相同。

    初始化std::shared_ptr的示例:

    1. //初始化方式1
    2. std::shared_ptr<int> sp1(new int(123));
    3. //初始化方式2
    4. std::shared_ptr<int> sp2;
    5. sp2.reset(new int(123));
    6. //初始化方式3
    7. std::shared_ptr<int> sp3;
    8. sp3 = std::make_shared<int>(123);
    1. class A
    2. {
    3. public:
    4. A()
    5. {
    6. std::cout << "A constructor" << std::endl;
    7. }
    8. ~A()
    9. {
    10. std::cout << "A destructor" << std::endl;
    11. }
    12. };
    13. int main()
    14. {
    15. {
    16. //初始化方式1
    17. std::shared_ptr sp1(new A());
    18. std::cout << "use count: " << sp1.use_count() << std::endl;
    19. //初始化方式2
    20. std::shared_ptr sp2(sp1);
    21. std::cout << "use count: " << sp1.use_count() << std::endl;
    22. sp2.reset();
    23. std::cout << "use count: " << sp1.use_count() << std::endl;
    24. {
    25. std::shared_ptr sp3 = sp1;
    26. std::cout << "use count: " << sp1.use_count() << std::endl;
    27. }
    28. std::cout << "use count: " << sp1.use_count() << std::endl;
    29. }
    30. return 0;
    31. }
    • 上述代码sp1构造时,同时触发对象A的构造,因此A的构造函数会执行;

    • 此时只有一个sp1对象引用sp1 new出来的A对象(为了叙述方便,下文统一称之为资源对象A),因此count第一次打印出来的引用计数值为1;

    • 利用sp1拷贝一份sp2,导致count第二次打印出来的引用计数为2;

    • 调用sp2的reset()方法,sp2释放对资源对象A的引用,因此coun第三次打印的引用计数值再次变为1;

    • 利用sp1再次创建sp3,因此count第四次打印的引用计数变为2;

    • sp3出了其作用域被析构,资源A的引用计数递减1,因此count第五次打印的引用计数为1;

    • sp1出了其作用域被析构,在其析构时递减资源A的引用计数至0,并析构资源A对象,因此类A的析构函数被调用。

    输出结果:

    1. A constructor
    2. use count: 1
    3. use count: 2
    4. use count: 1
    5. use count: 2
    6. use count: 1
    7. A destructor

    4、std::weak_ptr

    std::weak_ptr是一个不控制资源生命周期的智能指针,是对对象的一种弱引用,只是提供了对其管理的资源的一个访问手段,引入它的目的为协助std::shared_ptr工作。

    std::weak_ptr可以从一个std::shared_ptr或另一个std::weak_ptr对象构造,std::shared_ptr可以直接赋值给std::weak_ptr ,也可以通过std::weak_ptr的lock()函数来获得std::shared_ptr。它的构造和析构不会引起引用计数的增加或减少。std::weak_ptr可用来解决std::shared_ptr相互引用时的死锁问题,即两个std::shared_ptr相互引用,那么这两个指针的引用计数永远不可能下降为0, 资源永远不会释放。

    1. //创建一个std::shared_ptr对象
    2. std::shared_ptr<int> sp1(new int(123));
    3. std::cout << "use count: " << sp1.use_count() << std::endl;
    4. //通过构造函数得到一个std::weak_ptr对象
    5. std::weak_ptr<int> sp2(sp1);
    6. std::cout << "use count: " << sp1.use_count() << std::endl;
    7. //通过赋值运算符得到一个std::weak_ptr对象
    8. std::weak_ptr<int> sp3 = sp1;
    9. std::cout << "use count: " << sp1.use_count() << std::endl;
    10. //通过一个std::weak_ptr对象得到另外一个std::weak_ptr对象
    11. std::weak_ptr<int> sp4 = sp2;
    12. std::cout << "use count: " << sp1.use_count() << std::endl;
    1. use count: 1
    2. use count: 1
    3. use count: 1
    4. use count: 1

    无论通过何种方式创建std::weak_ptr都不会增加资源的引用计数,因此每次输出引用计数的值都是1。

    既然,std::weak_ptr不管理对象的生命周期,那么其引用的对象可能在某个时刻被销毁了,如何得知呢?std::weak_ptr提供了一个expired()方法来做这一项检测,返回true,说明其引用的资源已经不存在了;返回false,说明该资源仍然存在,这个时候可以使用std::weak_ptr 的lock()方法得到一个std::shared_ptr对象然后继续操作资源。

    std::weak_ptr类没有重写operator->和operator方法,因此不能像std::shared_ptr或std::unique_ptr一样直接操作对象,同时std::weak_ptr类也没有重写operator bool()操作,因此也不能通过std::weak_ptr对象直接判断其引用的资源是否存在。

    之所以std::weak_ptr不增加引用资源的引用计数来管理资源的生命周期,是因为即使它实现了以上说的几个方法,调用它们仍然是不安全的,因为在调用期间,引用的资源可能恰好被销毁了,这样可能会造成比较棘手的错误和麻烦。

    因此,std::weak_ptr的正确使用场景是那些资源如果可用就使用,如果不可用则不使用的场景,它不参与资源的生命周期管理。例如,网络分层结构中,Session对象(会话对象)利用Connection对象(连接对象)提供的服务来进行工作,但是Session对象不管理Connection对象的生命周期,Session管理Connection的生命周期是不合理的,因为网络底层出错会导致Connection对象被销毁,此时Session对象如果强行持有Connection对象则与事实矛盾。

    std::weak_ptr的应用场景,经典的例子是订阅者模式或者观察者模式中。这里以订阅者为例来说明,消息发布器只有在某个订阅者存在的情况下才会向其发布消息,而不能管理订阅者的生命周期。

  • 相关阅读:
    医疗服务全面升级,这个方法绝了!
    电脑上的文件怎么自动备份到网盘?
    土耳其商务团一行莅临优积科技考察交流
    再扩国产化信创版图!朗思科技与中科方德完成产品兼容性互认证
    【docker】WSL+docker_desktop+GPU配置环境失败
    二叉堆及堆排序详解保姆级教程略显罗嗦但保证能看懂
    d为何用模板参数
    掌握Spring条件装配的秘密武器
    Leo赠书活动-09期 【如何拿下数学】文末送书
    Himall商城Web帮助类删除、获取设置指定名称的Cookie特定键的值(2)
  • 原文地址:https://blog.csdn.net/lyc201219/article/details/126465099
  • 最新文章
  • 攻防演习之三天拿下官网站群
    数据安全治理学习——前期安全规划和安全管理体系建设
    企业安全 | 企业内一次钓鱼演练准备过程
    内网渗透测试 | Kerberos协议及其部分攻击手法
    0day的产生 | 不懂代码的"代码审计"
    安装scrcpy-client模块av模块异常,环境问题解决方案
    leetcode hot100【LeetCode 279. 完全平方数】java实现
    OpenWrt下安装Mosquitto
    AnatoMask论文汇总
    【AI日记】24.11.01 LangChain、openai api和github copilot
  • 热门文章
  • 十款代码表白小特效 一个比一个浪漫 赶紧收藏起来吧!!!
    奉劝各位学弟学妹们,该打造你的技术影响力了!
    五年了,我在 CSDN 的两个一百万。
    Java俄罗斯方块,老程序员花了一个周末,连接中学年代!
    面试官都震惊,你这网络基础可以啊!
    你真的会用百度吗?我不信 — 那些不为人知的搜索引擎语法
    心情不好的时候,用 Python 画棵樱花树送给自己吧
    通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!
    13 万字 C 语言从入门到精通保姆级教程2021 年版
    10行代码集2000张美女图,Python爬虫120例,再上征途
Copyright © 2022 侵权请联系2656653265@qq.com    京ICP备2022015340号-1
正则表达式工具 cron表达式工具 密码生成工具

京公网安备 11010502049817号