1. 必须要注意的 C++ 动态内存资源管理(五)——智能指针陷阱
  2.  
  3. 十三.小心使用智能指针。
  4.         在前面几节已经很详细了介绍了智能指针适用方式。看起来,似乎智能指针很强大,能够很方便很安全的管理我们的资源。然而其实不然,如果不恰当的使用智能指针有时候会在很不起眼的地方造成内存泄漏。在这一节中主要介绍在使用智能指针过程中有哪些地方需要注意,以及 shared_ptr 在使用上的缺陷。
  5.  
  6. 十四.使用智能指针的5个条款
  7. 条款1:不要把一个原生指针给多个shared_ptr或者unique_ptr管理
  8.         我们知道,在使用原生指针对智能指针初始化的时候,智能指针对象都视原生指针为自己管理的资源。换句话意思就说:初始化多个智能指针之后,这些智能指针都担负起释放内存的作用。那么就会导致该原生指针会被释放多次!!!
  9. int* ptr = new int;
  10. shared_ptr<int> p1(ptr);
  11. shared_ptr<int> p2(ptr);
  12. //p1,p2析构的时候都会释放ptr,同一内存被释放多次!
  13.  
  14. 条款2:不要把this指针交给智能指针管理
  15.         以下代码发生了什么事情呢?还是同样的错误。把原生指针 this 同时交付给了 m_sp p 管理,这样会导致 this 指针被 delete 两次。
  16.         这里值得注意的是:以上所说的交付给m_sp p 管理不对,并不是指不能多个shared_ptr同时占有同一类资源。shared_ptr之间的资源共享是通过shared_ptr智能指针拷贝、赋值实现的,因为这样可以引起计数器的更新;而如果直接通过原生指针来初始化,就会导致m_spp都根本不知道对方的存在,然而却两者都管理同一块地方。相当于”一间庙里请了两尊神”。
  17. class Test{
  18. public:
  19. void Do(){ m_sp = shared_ptr<Test>(this); }
  20. private:
  21. shared_ptr<Test> m_sp;
  22. };
  23. int main()
  24. {
  25. Test* t = new Test;
  26. shared_ptr<Test> p(t);
  27. p->Do();
  28. return ;
  29. }
  30.  
  31. 条款3:如果不是通过new得到的动态资源内存请自定义删除器
  32.         以下代码试图将malloc产生的动态内存交给shared_ptr管理;显然是有问题的,delete malloc 牛头不对马嘴!!!
  33.         所以我们需要自定义删除器[](int* p){ free(p); }传递给shared_ptr
  34. int main()
  35. {
  36. int* pi = (int*)malloc( * sizeof(int));
  37. shared_ptr<int> sp(pi);
  38. return ;
  39. }
  40.  
  41. 条款4:尽量不要使用get()
  42.         智能指针设计者之处提供get()接口是为了使得智能指针也能够适配原生指针使用的相关函数。这个设计可以说是个好的设计,也可以说是个失败的设计。因为根据封装的封闭原则,我们将原生指针交付给智能指针管理,我们就不应该也不能得到原生指针了;因为原生指针唯一的管理者就应该是智能指针。而不是客户逻辑区的其他什么代码。
  43.         所以我们在使用get()的时候要额外小心,禁止使用get()返回的原生指针再去初始化其他智能指针或者释放。(只能够被使用,不能够被管理)。而下面这段代码就违反了这个规定:
  44. int main()
  45. {
  46. shared_ptr<int> sp(new int());
  47. shared_ptr<int> pp(sp.get());
  48. return ;
  49. }
  50.  
  51. 条款5:尽量使用make_shared,不要把原生指针暴露出来
  52.         我们在定义shared_ptr智能指针的时候通常有3种方法:
  53.                 .先动态开辟内存,然后用局部变量接受指针。再把指针用于初始化。
  54.                 .直接在初始化参数中写new表达式。
  55.                 .使用make_shared函数。
  56.         实际应用中提倡使用第3中方法,第1种方法将原生指针暴露出来了,如果在外面的代码中不小心将该指针delete或者初始化其他的智能指针就会出现条款4的错误,所以这不是一个比较好的方法。第2种方法,直接在用new表达式作为实参,这样原生指针就匿名了。然而当你用new创建一个对象的同时创建一个shared_ptr时,这时会发生两次动态申请内存:一次是给使用new申请的对象本身的,而另一次则是由shared_ptr的构造函数引发的为资源管理对象分配的。与此相反,当你使用make_shared的时候,C++编译器只会一次性分配一个足够大的内存,用来保存这个资源管理者和这个新建对象。
  57.         下面是3种初始化shared_ptr的方法:
  58.  
  59. int main()
  60. {
  61. {
  62. //1.
  63. int *p = new int();
  64. shared_ptr<int> sp(p);
  65. }
  66. {
  67. //2.
  68. shared_ptr<int> sp(new int());
  69. }
  70. {
  71. //3.
  72. shared_ptr<int> sp = make_shared();
  73. }
  74. return ;
  75. }
  76.  
  77. 十五.shared_ptr的陷阱(缺陷)
  78.         以上所述的一些需要注意的地方都是可以通过代码规范而避免的问题,而接下来要说的东西可能是shared_ptr天生的缺陷。
  79.         我们知道shared_ptr最引以为豪的就是其计数功能,实现了只有当无使用者才会释放掉内存。让我们使用起来管理内存十分方便,然而在使用过程中可能会不经意之间造成内存泄漏而且不容易查找。而这个问题就是 —— 循环引用。
  80.         一旦代码中出现了循环引用,那么基于计数的共享机制将会被彻底击败!先看下面的代码:
  81. class B;
  82. class A
  83. {
  84. public:
  85.   shared_ptr<B> m_b;
  86. };
  87. class B
  88. {
  89. public:
  90.   shared_ptr<A> m_a;
  91. };
  92.  
  93. int main()
  94. {
  95.   {
  96.     shared_ptr<A> a(new A); //new出来的A的引用计数此时为1
  97.     shared_ptr<B> b(new B); //new出来的B的引用计数此时为1
  98.     a->m_b = b; //B的引用计数增加为2
  99.     b->m_a = a; //A的引用计数增加为2
  100.   }
  101.   //b先出作用域,B的引用计数减少为1,不为0;
  102.   //所以堆上的B空间没有被释放,且B持有的A也没有机会被析构,A的引用计数也完全没减少
  103.  
  104.   //a后出作用域,同理A的引用计数减少为1,不为0,所以堆上A的空间也没有被释放
  105. }
  106.  
  107.         可以看出来以上的代码中A对象中指针引用B对象,B对象指针引用A对象,这样就导致了循环引用的出现。而代码运行到最后,由于两个指针计数都没有到0,所以资源无法释放导致了内存泄漏!
  108.         导致这样结果的原因就是:AB都互相指着对方吼,“放开我的引用!“,“你先发我的我就放你的!”,于是悲剧发生了。
  109.         所以在使用基于引用计数的智能指针时,要特别小心循环引用带来的内存泄漏,循环引用不只是两方的情况,只要引用链成环都会出现问题。当然循环引用本身就说明设计上可能存在一些问题,如果特殊原因不得不使用循环引用,那可以让引用链上的一方持用普通指针(或弱智能指针weak_ptr)即可。
  110.         不过这个时候有人可能会说,真正业务逻辑中会出现这样的代码吗?答案是肯定的,举个简单的例子,链表!,一旦尾首相连形成循环链表的时候那么就出现了循环引用,所以使用shared_ptr的时候一定要先判断是否会出现循环引用。
  111.  
  112. 前面几节基本上介绍了c++动态内存管理方案以及注意事项,后面我们会再花一节介绍一个例子来真正地将这些知识点用于实践!

https://blog.csdn.net/y1196645376/article/details/53010975

必须要注意的 C++ 动态内存资源管理(五)——智能指针陷阱的更多相关文章

  1. c++动态内存管理与智能指针

    目录 一.介绍 二.shared_ptr类 make_shared函数 shared_ptr的拷贝和引用 shared_ptr自动销毁所管理的对象- -shared_ptr还会自动释放相关联对象的内存 ...

  2. 必须要注意的 C++ 动态内存资源管理(一)——视资源为对象

    必须要注意的 C++ 动态内存资源管理(一)——视资源为对象 一.前言         所谓资源就是,一旦你用了它,将来必须还给系统.如果不这样,糟糕的事情就会发生.C++ 程序中最常见使用的资源就是 ...

  3. 必须要注意的 C++ 动态内存资源管理(二)——指针对象简单实现

    必须要注意的 C++动态内存资源管理(二)——指针对象简单实现 四.拷贝类型的资源         上节我们说过,对于图片类型的资源我们有时候往往采用拷贝(如果对于那种公共图片,可能采用唯一副本,提供 ...

  4. 必须要注意的 C++ 动态内存资源管理(六)——vector的简单实现

    必须要注意的 C++ 动态内存资源管理(六)——vector的简单实现 十六.myVector分析         我们知道,vector类将其元素存放在连续的内存中.为了获得可接受的性能,vetor ...

  5. C++ 拷贝控制和资源管理,智能指针的简单实现

    C++ 关于拷贝控制和资源管理部分的笔记,并且介绍了部分C++ 智能指针的概念,然后实现了一个基于引用计数的智能指针.关于C++智能指针部分,后面会有专门的研究. 通常,管理类外资源的类必须定义拷贝控 ...

  6. C++ Primer : 第十二章 : 动态内存之shared_ptr与new的结合使用、智能指针异常

    shared_ptr和new结合使用 一个shared_ptr默认初始化为一个空指针.我们也可以使用new返回的指针来初始化一个shared_ptr: shared_ptr<double> ...

  7. C++ Primer 5th 第12章 动态内存

    练习12.1:在此代码的结尾,b1 和 b2 各包含多少个元素? StrBlob b1; { StrBlob b2 = {"a", "an", "th ...

  8. C++Primer学习——动态内存

    静态内存:用来保存static 栈内存:保存非static 智能指针: shared_ptr:允许多个指针指向一个对象 unique_ptr:独占所指对象 weak_ptr:一种弱引用,指向share ...

  9. C/C++基础----动态内存

    why 管理较难,忘记释放会内存泄漏,提早释放可能非法引用,重复释放. 为了更容易,更安全的使用动态内存,提供智能指针,其默认初始化保存一个空指针. what shared_ptr允许多个指针指向同一 ...

随机推荐

  1. android studio中为gradle指定cmake版本

    Android Studio相当于是Intellij基础上写了一个AS插件,这个插件使用gradle作为构建系统,因此构建出现问题先考虑gradle的文档. gradle可以使用native buil ...

  2. 06-jQuery进阶

    本篇主要介绍jQuery的正则.冒泡事件.委托事件.以及DOM操作.JavaScript对象以及ajax等知识: 一.正则 简而言之,正则的规则无论是各种语言均是通用的,故其规则中的字符便不再介绍了, ...

  3. 【linux-command】Chrome安装linux-command插件

    一.linux-command是什么 550 多个 Linux 命令,内容包含 Linux 命令手册.详解.学习,值得收藏的 Linux 命令速查手册.Githb地址: https://github. ...

  4. 51nod 1053 最大M子段和 V2

    N个整数组成的序列a[1],a[2],a[3],…,a[n],将这N个数划分为互不相交的M个子段,并且这M个子段的和是最大的.如果M >= N个数中正数的个数,那么输出所有正数的和. 例如:-2 ...

  5. es6 字符串模板拼接和传统字符串拼接

    字符串拼接是在日常开发中必不可少的一个环节. 注意:字符串可以用单引号'',或者""双引号,出于方便大家理解,文章以下内容统一使用单引号''! 如果只是一个字符串和一个变量拼接,使 ...

  6. Python应用之-修改通讯录

    #-*- coding:utf-8 -*- import sqlite3 #打开本地数据库用于存储用户信息 conn = sqlite3.connect('mysql_person.db') #在该数 ...

  7. Flume高级之自定义MySQLSource

    1 自定义Source说明 Source是负责接收数据到Flume Agent的组件.Source组件可以处理各种类型.各种格式的日志数据,包括avro.thrift.exec.jms.spoolin ...

  8. 讲解Flume

    Spark Streaming通过push模式和pull模式两种模式来集成Flume push模式:Spark Streaming端会启动一个基于Avro Socket Server的Receiver ...

  9. django-自定义文件上传存储类

    文件储存API:https://yiyibooks.cn/xx/django_182/ref/files/storage.html 编写自定义存储系统:https://yiyibooks.cn/xx/ ...

  10. 4-html图片与链接

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...