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


            智能指针是存储指向动态分配(堆)对象指针的类。用于生存期控制,能够确保在离开指针所在作用域时,自动正确的销毁动态分配的对象,防止内存泄漏。它的一种通用实现技术是使用引用计数。美使用它一次,内部的引用计数加1,每析构一次,内部引用计数减1,减为0时,删除所指向的堆内存。

    shared_ptr共享的智能指针

            std::shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。在最后一个shared_ptr析构的时候,内存才会被释放。

    shared_ptr的基本用法

    初始化

            可以通过构造函数,std::make_shared辅助函数和reset方法来初始化shared_ptr,代码如下:

    1. #include
    2. #include
    3. using namespace std;
    4. int main()
    5. {
    6. std::shared_ptr<int> p(new int(1));
    7. std::shared_ptr<int> p2 = p;
    8. std::shared_ptr<int> ptr;
    9. ptr.reset(new int(1));
    10. if (ptr)
    11. {
    12. cout << "ptr is not null" << endl;
    13. }
    14. ///编译报错,不允许直接赋值
    15. ///std::shared_ptr p4 = new int(1);
    16. return 0;
    17. }

            我们应该优先使用make_shared来构造智能指针,因为它更加高效。

            不能将一个原始指针直接赋值给一个指针指针,例如,下面这种方法是错误的:

    1. ///编译报错,不允许直接赋值
    2. std::shared_ptr<int> p = new int(1);

            可以看到智能指针的用法和普通指针的用法类似,只不过不需要自己管理分配的内存。shared_ptr不能通过直接将原始指针赋值来初始化,需要通过构造函数和辅助方法来初始化。对于一个未初始化的智能指针,可以通过reset方法来初始化,当智能指针中有值的时候,调用reset会使引用计数减1。另外,智能指针可以通过重载的bool类型符来判断智能智能中是否为空(未初始化)。

    获取原始指针

            当需要获取原始指针时,可以通过get方法来返回原始指针,代码如下:

    1. #include
    2. #include
    3. #include
    4. using namespace std;
    5. int main()
    6. {
    7. std::shared_ptr<int> p(new int(1));
    8. int* pp = p.get();
    9. cout << *pp << endl;
    10. std::shared_ptr pstr(new string("hello world"));
    11. auto ppstr = pstr.get();
    12. cout << *ppstr << endl;
    13. return 0;
    14. }

    指定删除器

            智能指针初始化可以指定删除器,代码如下:

    1. #include
    2. #include
    3. #include
    4. #include
    5. using namespace std;
    6. typedef struct Message
    7. {
    8. char* ptr;
    9. int i;
    10. double db;
    11. }Message;
    12. void DeletePtr(Message* pm)
    13. {
    14. cout << pm->i << endl;
    15. cout << pm->db << endl;
    16. cout << pm->ptr << endl;
    17. if (pm->ptr)
    18. {
    19. delete [] pm->ptr;
    20. pm->ptr = nullptr;
    21. }
    22. }
    23. int main()
    24. {
    25. Message* m = new Message;
    26. m->db = 2.8;
    27. m->i = 1;
    28. m->ptr = new char[100];
    29. memset(m->ptr, 0, 100);
    30. char* ptr = (char*)"hello world";
    31. strncpy(m->ptr, ptr, strlen(ptr));
    32. std::shared_ptr p(m, DeletePtr);
    33. ///std::shared_ptr pp(m, [](Message* pm){if (pm->ptr) { cout << pm->ptr << endl; delete [] pm->ptr;}});
    34. return 0;
    35. }

            当p的引用计数为0时,自动调用删除器DeletePtr来释放对象的内存。删除器可以是一个lambda表达式,因此,上面的写法还可以改为:

    std::shared_ptr pp(m, [](Message* pm){if (pm->ptr) { cout << pm->ptr << endl; delete [] pm->ptr;}});

    使用shared_ptr需要注意的问题

            智能指针虽然能自动管理堆内存,但是它哟不少缺陷,在使用时需要注意。

            1、不要用一个原始指针初始化多个shared_ptr,例如下面这些是错误的:

    1. int* ptr = new int;
    2. shared_ptr<int> p1(ptr);
    3. shared_ptr<int> p2(ptr); ///logic error

            2、不要在函数实参中创建shared_ptr,对于下面的写法是错误的:

    function(shared_ptr<int>(new int), g());

            因为C++的函数参数的计算顺序在不同的编译器不同的调用约定下可能是不一样的,一般是从右到左,但也有可能是从左到右,所以,可能的过程是先new int,然后调用g(),如果恰好g()发生异常,而shared_ptr还没有创建,则int内存泄漏,正确的写法是先创建智能指针,代码如下:

    1. shared_ptr<int> p(new int);
    2. f(p, g());

            3、通过shared_from_this()返回this指针。不要将this指针作为shared_ptr返回出来,因为this指针本质上是一个裸指针,因此,这样可能会导致重复析构,看下面的代码:

    1. #include
    2. #include
    3. #include
    4. #include
    5. using namespace std;
    6. struct A
    7. {
    8. shared_ptr GetSelf()
    9. {
    10. return shared_ptr(this);
    11. }
    12. ///析构函数调用了2次
    13. ~A()
    14. {
    15. cout << "delete A" << endl;
    16. }
    17. };
    18. int main()
    19. {
    20. shared_ptr sp1(new A);
    21. shared_ptr sp2 = sp1->GetSelf();
    22. return 0;
    23. }

            在这个例子中,由于用同一个指针(this)构造了两个智能指针sp1和sp2,而他们之间是没有任何关系的,在离开作用域之后this将会被构造的两个智能指针各自析构,导致重复析构的错误。

            正确返回this的shared_ptr的做法是:让目标类通过派生std::enable_shared_this类,然后使用基类的成员函数shared_from_this来返回this的shared_ptr,看下面的代码:

    1. #include
    2. #include
    3. #include
    4. #include
    5. using namespace std;
    6. struct A : public std::enable_shared_from_this
    7. {
    8. shared_ptr GetSelf()
    9. {
    10. return shared_from_this();
    11. }
    12. ///析构函数调用了1次
    13. ~A()
    14. {
    15. cout << "delete A" << endl;
    16. }
    17. };
    18. int main()
    19. {
    20. shared_ptr sp1(new A);
    21. shared_ptr sp2 = sp1->GetSelf();
    22. return 0;
    23. }

            4、要避免循环引用。指针指针最大的一个陷阱是循环引用,循环引用会导致内存泄漏。下面是一个典型的循环引用的场景。

    1. #include
    2. #include
    3. #include
    4. #include
    5. using namespace std;
    6. struct A;
    7. struct B;
    8. struct A
    9. {
    10. std::shared_ptr bptr;
    11. ~A()
    12. {
    13. cout << "A is deleted" << endl;
    14. }
    15. };
    16. struct B
    17. {
    18. std::shared_ptr aptr;
    19. ~B()
    20. {
    21. cout << "B is deleted" << endl;
    22. }
    23. };
    24. int main()
    25. {
    26. {
    27. ///析构函数没有被调用
    28. shared_ptr ap(new A);
    29. shared_ptr bp(new B);
    30. ap->bptr = bp;
    31. bp->aptr = ap;
    32. }
    33. return 0;
    34. }

            测试结果是两个指针A和B都不会被删除,存在内存泄漏。循环引用导致ap和bp引用计算器为2,在离开作用域之后,ap和bp的引用计数器减为1,并不会为0,导致两个指针不会被析构,产生了内存泄漏。

  • 相关阅读:
    【大虾送书第十期】从不了解用户画像,到用画像数据赋能业务看这一本书就够了
    Transwarp Inceptor中的对象
    使用LRU加速python应用
    Java封装之this关键字、static关键字、包简述
    工单提交管理H5小程序开发
    iscsi_server_client_chap_username_password_targetcli
    基于FPGA的图像拉普拉斯变换实现,包括tb测试文件和MATLAB辅助验证
    前端导出下载文件后提示无法打开文件
    Servlet
    【附源码】Python计算机毕业设计企业人事管理系统
  • 原文地址:https://blog.csdn.net/qq_25048473/article/details/134474753
  • 最新文章
  • 攻防演习之三天拿下官网站群
    数据安全治理学习——前期安全规划和安全管理体系建设
    企业安全 | 企业内一次钓鱼演练准备过程
    内网渗透测试 | 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号