必须要注意的 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. EFK(Elasticsearch+Filebeat+Kibana)收集容器日志

    介绍 Elasticsearch 是一个实时的.分布式的可扩展的搜索引擎,允许进行全文.结构化搜索,它通常用于索引和搜索大量日志数据,也可用于搜索许多不同类型的文档. Beats 是数据采集的得力工具 ...

  2. Linux无法被远程登录;用户的关机, 重启,注销,新增用户,删除用户

    不能使用xshell连接到我的Linux服务器 通过再windows的cmd中ping了我的Linux地址,发现网络不通. 查看百度发现是因为网络没有选桥接模式,然后选完桥接模式告诉我: 然后发现没有 ...

  3. 自动化运维-ansible入门篇

    1.ansible配置 什么是Ansible IT自动化工具 依赖于现有的操作系统凭证来访问控制远程机器 简单易用.安全可靠 Ansible可以完成哪些任务 配置系统 开发软件 编排高级的IT任务 A ...

  4. Pthon面向对象-异常处理

    Pthon面向对象-异常处理 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.异常概述 1>.错误(Error) 逻辑错误: 算法写错了,例如加法写成了减法. 笔误: 例如 ...

  5. 项目Alpha冲刺——集合

    作业描述 课程: 软件工程1916|W(福州大学) 作业要求: 项目Alpha冲刺(团队) 团队名称: 火鸡堂 作业目标: 完成项目Alpha冲刺 团队信息 队名:火鸡堂 队员学号 队员姓名 博客地址 ...

  6. windows 10 下使用Navicat for oracle 数据库还原

    一.前期准备 1.安装windows 10系统 2.安装oracle 11g 数据库 3.安装PLsql(也不需要) 4.安装sqlplus(这个必须有) 5.使用下面这个东西新建数据库(不懂创建的话 ...

  7. 从输入URL到页面返回的过程详解

    文章转自以为大神的博客;https://www.cnblogs.com/xianyulaodi/p/6547807.html#_labelTop 总结的很不错,看完收获颇多, 下面就是大神的文章,我只 ...

  8. include指令 include动作

  9. 公告 & 备注

    公告 这个\(blog\)从\(2019.12.21\)正式开始使用. 之前的博客请出门右转链接: \[\Large\texttt{my blog}\] \(:)\) 备注 近期要学的算法qwq \( ...

  10. 2019-2020-1 20199302《Linux内核原理与分析》第七周作业

    第六章 进程的描述和进程的创建 (一)进程的描述 1.OS的三大管理功能:(1)进程管理(进程)(2)内存管理(虚拟内存)(3)文件系统(文件) 2.进程的描述:进程控制块PCB 3.在Linux内核 ...