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

十三.小心使用智能指针。
        在前面几节已经很详细了介绍了智能指针适用方式。看起来,似乎智能指针很强大,能够很方便很安全的管理我们的资源。然而其实不然,如果不恰当的使用智能指针有时候会在很不起眼的地方造成内存泄漏。在这一节中主要介绍在使用智能指针过程中有哪些地方需要注意,以及 shared_ptr 在使用上的缺陷。 十四.使用智能指针的5个条款
条款1:不要把一个原生指针给多个shared_ptr或者unique_ptr管理
        我们知道,在使用原生指针对智能指针初始化的时候,智能指针对象都视原生指针为自己管理的资源。换句话意思就说:初始化多个智能指针之后,这些智能指针都担负起释放内存的作用。那么就会导致该原生指针会被释放多次!!!
int* ptr = new int;
shared_ptr<int> p1(ptr);
shared_ptr<int> p2(ptr);
//p1,p2析构的时候都会释放ptr,同一内存被释放多次! 条款2:不要把this指针交给智能指针管理
        以下代码发生了什么事情呢?还是同样的错误。把原生指针 this 同时交付给了 m_sp 和 p 管理,这样会导致 this 指针被 delete 两次。
        这里值得注意的是:以上所说的交付给m_sp 和 p 管理不对,并不是指不能多个shared_ptr同时占有同一类资源。shared_ptr之间的资源共享是通过shared_ptr智能指针拷贝、赋值实现的,因为这样可以引起计数器的更新;而如果直接通过原生指针来初始化,就会导致m_sp和p都根本不知道对方的存在,然而却两者都管理同一块地方。相当于”一间庙里请了两尊神”。
class Test{
public:
void Do(){ m_sp = shared_ptr<Test>(this); }
private:
shared_ptr<Test> m_sp;
};
int main()
{
Test* t = new Test;
shared_ptr<Test> p(t);
p->Do();
return ;
} 条款3:如果不是通过new得到的动态资源内存请自定义删除器
        以下代码试图将malloc产生的动态内存交给shared_ptr管理;显然是有问题的,delete 和 malloc 牛头不对马嘴!!!
        所以我们需要自定义删除器[](int* p){ free(p); }传递给shared_ptr。
int main()
{
int* pi = (int*)malloc( * sizeof(int));
shared_ptr<int> sp(pi);
return ;
} 条款4:尽量不要使用get()
        智能指针设计者之处提供get()接口是为了使得智能指针也能够适配原生指针使用的相关函数。这个设计可以说是个好的设计,也可以说是个失败的设计。因为根据封装的封闭原则,我们将原生指针交付给智能指针管理,我们就不应该也不能得到原生指针了;因为原生指针唯一的管理者就应该是智能指针。而不是客户逻辑区的其他什么代码。
        所以我们在使用get()的时候要额外小心,禁止使用get()返回的原生指针再去初始化其他智能指针或者释放。(只能够被使用,不能够被管理)。而下面这段代码就违反了这个规定:
int main()
{
shared_ptr<int> sp(new int());
shared_ptr<int> pp(sp.get());
return ;
} 条款5:尽量使用make_shared,不要把原生指针暴露出来
        我们在定义shared_ptr智能指针的时候通常有3种方法:
                .先动态开辟内存,然后用局部变量接受指针。再把指针用于初始化。
                .直接在初始化参数中写new表达式。
                .使用make_shared函数。
        实际应用中提倡使用第3中方法,第1种方法将原生指针暴露出来了,如果在外面的代码中不小心将该指针delete或者初始化其他的智能指针就会出现条款4的错误,所以这不是一个比较好的方法。第2种方法,直接在用new表达式作为实参,这样原生指针就匿名了。然而当你用new创建一个对象的同时创建一个shared_ptr时,这时会发生两次动态申请内存:一次是给使用new申请的对象本身的,而另一次则是由shared_ptr的构造函数引发的为资源管理对象分配的。与此相反,当你使用make_shared的时候,C++编译器只会一次性分配一个足够大的内存,用来保存这个资源管理者和这个新建对象。
        下面是3种初始化shared_ptr的方法: int main()
{
{
//1.
int *p = new int();
shared_ptr<int> sp(p);
}
{
//2.
shared_ptr<int> sp(new int());
}
{
//3.
shared_ptr<int> sp = make_shared();
}
return ;
} 十五.shared_ptr的陷阱(缺陷)
        以上所述的一些需要注意的地方都是可以通过代码规范而避免的问题,而接下来要说的东西可能是shared_ptr天生的缺陷。
        我们知道shared_ptr最引以为豪的就是其计数功能,实现了只有当无使用者才会释放掉内存。让我们使用起来管理内存十分方便,然而在使用过程中可能会不经意之间造成内存泄漏而且不容易查找。而这个问题就是 —— 循环引用。
        一旦代码中出现了循环引用,那么基于计数的共享机制将会被彻底击败!先看下面的代码:
class B;
class A
{
public:
  shared_ptr<B> m_b;
};
class B
{
public:
  shared_ptr<A> m_a;
}; int main()
{
  {
    shared_ptr<A> a(new A); //new出来的A的引用计数此时为1
    shared_ptr<B> b(new B); //new出来的B的引用计数此时为1
    a->m_b = b; //B的引用计数增加为2
    b->m_a = a; //A的引用计数增加为2
  }
  //b先出作用域,B的引用计数减少为1,不为0;
  //所以堆上的B空间没有被释放,且B持有的A也没有机会被析构,A的引用计数也完全没减少   //a后出作用域,同理A的引用计数减少为1,不为0,所以堆上A的空间也没有被释放
}         可以看出来以上的代码中A对象中指针引用B对象,B对象指针引用A对象,这样就导致了循环引用的出现。而代码运行到最后,由于两个指针计数都没有到0,所以资源无法释放导致了内存泄漏!
        导致这样结果的原因就是:A和B都互相指着对方吼,“放开我的引用!“,“你先发我的我就放你的!”,于是悲剧发生了。
        所以在使用基于引用计数的智能指针时,要特别小心循环引用带来的内存泄漏,循环引用不只是两方的情况,只要引用链成环都会出现问题。当然循环引用本身就说明设计上可能存在一些问题,如果特殊原因不得不使用循环引用,那可以让引用链上的一方持用普通指针(或弱智能指针weak_ptr)即可。
        不过这个时候有人可能会说,真正业务逻辑中会出现这样的代码吗?答案是肯定的,举个简单的例子,链表!,一旦尾首相连形成循环链表的时候那么就出现了循环引用,所以使用shared_ptr的时候一定要先判断是否会出现循环引用。 前面几节基本上介绍了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. 函数式接口(Functional Interface)

    原文链接:https://www.cnblogs.com/runningTurtle/p/7092632.html 阅读目录 什么是函数式接口(Functional Interface) 函数式接口用 ...

  2. 使用三层交换实现不同网段、不同 VLAN 互通

    上一篇实现了使用Trunk做跨交换机VLAN通信,这一篇就试试使用三层交换实现不同网段,不同VLAN间的通信. 实验拓扑 在一台三层交换机下面连接一台二层交换机,再在二层交换机下面连接两台VPC,地址 ...

  3. 关于struct和typedef struct

    以 struct TelPhone{ ]; ]; }; 为例 这里先定义了一个 TelPhone的结构体. 加入需要为TelPhone定义一个别名: 其语法为 typedef TelPhone TP: ...

  4. git拉取远程分支并切换到该分支

    整理了五种方法,我常用最后一种,这五种方法(除了第4中已经写了fetch的步骤)执行前都需要执行git fetch来同步远程仓库 (1)git checkout -b 本地分支名 origin/远程分 ...

  5. PAT甲级1003题解——Dijkstra

    解题步骤: 1.初始化:设置mat[][]存放点之间的距离,vis[]存放点的选取情况,people[]存放初始时每个城市的人数,man[]存放到达每个城市的救援队的最多的人数,num[]存放到达每个 ...

  6. python开发笔记之zip()函数用法详解

    今天分享一篇关于python下的zip()函数用法. zip()是Python的一个内建函数,它接受一系列可迭代的对象作为参数,将对象中对应的元素按顺序组合成一个tuple,每个tuple中包含的是原 ...

  7. py3+requests+json+xlwt,爬取拉勾招聘信息

    在拉勾搜索职位时,通过谷歌F12抓取请求信息 发现请求是一个post请求,参数为: 返回的是json数据 有了上面的基础,我们就可以构造请求了 然后对获取到的响应反序列化,这样就获取到了json格式的 ...

  8. 阿里巴巴编程规约--digest

    所谓卫语句,如果某个条件极其罕见,就应该单独检查该条件,并在该条件为真时立刻从函数中返回.这样的单独检查常常被称为“卫语句”. 微服务之间将DTO,Req放到一个单独的项目中,相关的项目都依赖这个底层 ...

  9. 全局异常捕获处理-@ControllerAdvice+@HandleException

    涂涂影院管理系统这个demo中有个异常管理的标签,用于捕获 涂涂影院APP用户异常信息 ,有小伙伴好奇,排除APP,后台端的是如何处理全局异常的,故项目中的实际应用已记之. 关于目前的异常处理 在使用 ...

  10. OLED液晶屏幕(3)串口读取文字并分割

    https://blog.csdn.net/iracer/article/details/50334041 String comdata = ""; void setup() { ...