zoukankan      html  css  js  c++  java
  • C++ 中shared_ptr循环引用计数问题

    转自 https://blog.csdn.net/daniel_ustc/article/details/23096229

    c++智能指针介绍

    由于 C++ 语言没有自动内存回收机制,程序员每次 new 出来的内存都要手动 delete,比如流程太复杂,最终导致没有 delete,异常导致程序过早退出,没有执行 delete 的情况并不罕见,并造成内存泄露。如此c++引入智能指针 ,智能指针即是C++ RAII的一种应用,可用于动态资源管理,资源即对象的管理策略。 智能指针在 <memory> 标头文件的 std 命名空间中定义。 它们对 RAII获取资源即初始化编程惯用法至关重要。RAII 的主要原则是为所有堆分配资源提供所有权,例如动态分配内存或系统对象句柄、析构函数包含要删除或释放资源的代码的堆栈分配对象,以及任何相关清理代码。

    c++智能指针类别

    c++ 智能指针主要包括:unique_ptr,shared_ptr, weak_ptr, 这三种,其中auto_ptr 已被遗弃。

    unique_ptr
    只允许基础指针的一个所有者。 可以移到新所有者(具有移动语义),但不会复制或共享(即我们无法得到指向同一个对象的两个unique_ptr)。 替换已弃用的 auto_ptr。 相较于 boost::scoped_ptr。 unique_ptr 小巧高效;大小等同于一个指针,支持 rvalue 引用,从而可实现快速插入和对 STL 集合的检索。 头文件:<memory>。

    使用unique_ptr,可以实现以下功能:

    1、为动态申请的内存提供异常安全。
    2、将动态申请内存的所有权传递给某个函数。
    3、从某个函数返回动态申请内存的所有权。

    4、在容器中保存指针。
    5、所有auto_ptr应该具有的(但无法在C++ 03中实现的)功能。

    如下代码所示:

    1. class A;
    2. // 如果程序执行过程中抛出了异常,unique_ptr就会释放它所指向的对象
    3. // 传统的new 则不行
    4. unique_ptr<A> fun1()
    5. {
    6. unique_ptr p(new A);
    7. //do something
    8. return p;
    9. }
    10. void fun2()
    11. { // unique_ptr具有移动语义
    12. unique_ptr<A> p = f();// 使用移动构造函数
    13. // do something
    14. }// 在函数退出的时候,p以及它所指向的对象都被删除释放
     shared_ptr
    采用引用计数的智能指针。 shared_ptr基于“引用计数”模型实现,多个shared_ptr可指向同一个动态对象,并维护了一个共享的引用计数器,记录了引用同一对象的shared_ptr实例的数量。当最后一个指向动态对象的shared_ptr销毁时,会自动销毁其所指对象(通过delete操作符)。shared_ptr的默认能力是管理动态内存,但支持自定义的Deleter以实现个性化的资源释放动作。头文件:<memory>。

    基本操作:shared_ptr的创建、拷贝、绑定对象的变更(reset)、shared_ptr的销毁(手动赋值为nullptr或离开作用域)、指定deleter等操作。

     shared_ptr的创建,有两种方式,一,使用函数make_shared(会根据传递的参数调用动态对象的构造函数);二,使用构造函数(可从原生指针、unique_ptr、另一个shared_ptr创建)

    1. shared_ptr<int> p1 = make_shared<int>(1);// 通过make_shared函数
    2. shared_ptr<int> p2(new int(2));// 通过原生指针构造
    此外智能指针若为“空“,即不指向任何对象,则为false,否则为true,可作为条件判断。可以通过两种方式指定deleter,一是构造shared_ptr时,二是使用reset方法时。可以重载的operator->, operator *,以及其他辅助操作如unique()、use_count(), get()等成员方法。

     weak_ptr
    结合 shared_ptr 使用的特例智能指针。 weak_ptr 提供对一个或多个 shared_ptr 实例所属对象的访问,但是,不参与引用计数。 如果您想要观察对象但不需要其保持活动状态,请使用该实例。 在某些情况下需要断开 shared_ptr 实例间的循环引用。 头文件:<memory>。

    weak_ptr的用法如下:

    weak_ptr用于配合shared_ptr使用,并不影响动态对象的生命周期,即其存在与否并不影响对象的引用计数器。weak_ptr并没有重载operator->和operator *操作符,因此不可直接通过weak_ptr使用对象。提供了expired()与lock()成员函数,前者用于判断weak_ptr指向的对象是否已被销毁,后者返回其所指对象的shared_ptr智能指针(对象销毁时返回”空“shared_ptr)。循环引用的场景:如二叉树中父节点与子节点的循环引用,容器与元素之间的循环引用等。

    智能指针的循环引用

    循环引用问题可以参考这个链接上的问题理解,“循环引用”简单来说就是:两个对象互相使用一个shared_ptr成员变量指向对方的会造成循环引用。导致引用计数失效。下面给段代码来说明循环引用:

    1. #include <iostream>
    2. #include <memory>
    3. using namespace std;
    4. class B;
    5. class A
    6. {
    7. public:// 为了省去一些步骤这里 数据成员也声明为public
    8. //weak_ptr<B> pb;
    9. shared_ptr<B> pb;
    10. void doSomthing()
    11. {
    12. // if(pb.lock())
    13. // {
    14. //
    15. // }
    16. }
    17. ~A()
    18. {
    19. cout << "kill A ";
    20. }
    21. };
    22. class B
    23. {
    24. public:
    25. //weak_ptr<A> pa;
    26. shared_ptr<A> pa;
    27. ~B()
    28. {
    29. cout <<"kill B ";
    30. }
    31. };
    32. int main(int argc, char** argv)
    33. {
    34. shared_ptr<A> sa(new A());
    35. shared_ptr<B> sb(new B());
    36. if(sa && sb)
    37. {
    38. sa->pb=sb;
    39. sb->pa=sa;
    40. }
    41. cout<<"sa use count:"<<sa.use_count()<<endl;
    42. return 0;
    43. }
    上面的代码运行结果为:sa use count:2, 注意此时sa,sb都没有释放,产生了内存泄露问题!!!

    即A内部有指向B,B内部有指向A,这样对于A,B必定是在A析构后B才析构,对于B,A必定是在B析构后才析构A,这就是循环引用问题,违反常规,导致内存泄露。

    一般来讲,解除这种循环引用有下面有三种可行的方法(参考):
    1. 当只剩下最后一个引用的时候需要手动打破循环引用释放对象。
    2. 当A的生存期超过B的生存期的时候,B改为使用一个普通指针指向A。
    3. 使用弱引用的智能指针打破这种循环引用。
    虽然这三种方法都可行,但方法1和方法2都需要程序员手动控制,麻烦且容易出错。我们一般使用第三种方法:弱引用的智能指针weak_ptr。

    强引用和弱引用
    一个强引用当被引用的对象活着的话,这个引用也存在(就是说,当至少有一个强引用,那么这个对象就不能被释放)。share_ptr就是强引用。相对而言,弱引用当引用的对象活着的时候不一定存在。仅仅是当它存在的时候的一个引用。弱引用并不修改该对象的引用计数,这意味这弱引用它并不对对象的内存进行管理,在功能上类似于普通指针,然而一个比较大的区别是,弱引用能检测到所管理的对象是否已经被释放,从而避免访问非法内存。

    使用weak_ptr来打破循环引用

    代码如下:

    1. #include <iostream>
    2. #include <memory>
    3. using namespace std;
    4. class B;
    5. class A
    6. {
    7. public:// 为了省去一些步骤这里 数据成员也声明为public
    8. weak_ptr<B> pb;
    9. //shared_ptr<B> pb;
    10. void doSomthing()
    11. {
    12. shared_ptr<B> pp = pb.lock();
    13. if(pp)//通过lock()方法来判断它所管理的资源是否被释放
    14. {
    15. cout<<"sb use count:"<<pp.use_count()<<endl;
    16. }
    17. }
    18. ~A()
    19. {
    20. cout << "kill A ";
    21. }
    22. };
    23. class B
    24. {
    25. public:
    26. //weak_ptr<A> pa;
    27. shared_ptr<A> pa;
    28. ~B()
    29. {
    30. cout <<"kill B ";
    31. }
    32. };
    33. int main(int argc, char** argv)
    34. {
    35. shared_ptr<A> sa(new A());
    36. shared_ptr<B> sb(new B());
    37. if(sa && sb)
    38. {
    39. sa->pb=sb;
    40. sb->pa=sa;
    41. }
    42. sa->doSomthing();
    43. cout<<"sb use count:"<<sb.use_count()<<endl;
    44. return 0;
    45. }

    需要知道的

    weak_ptr除了对所管理对象的基本访问功能(通过get()函数)外,还有两个常用的功能函数:expired()用于检测所管理的对象是否已经释放;lock()用于获取所管理的对象的强引用指针。不能直接通过weak_ptr来访问资源。那么如何通过weak_ptr来间接访问资源呢?答案是:在需要访问资源的时候weak_ptr为你生成一个shared_ptr,shared_ptr能够保证在shared_ptr没有被释放之前,其所管理的资源是不会被释放的。创建shared_ptr的方法就是lock()方法。

    参考

    http://msdn.microsoft.com/zh-cn/library/hh279674.aspx

    http://www.dewen.org/q/8560/%E5%85%B3%E4%BA%8E%E9%81%BF%E5%85%8D%E5%BE%AA%E7%8E%AF%E5%BC%95%E7%94%A8

  • 相关阅读:
    c++错误崩溃3
    c++崩溃错误2
    c++多线程崩溃错误1
    MySQL UNSIGNED
    2PC和3PC
    proxy-target-class="false"与proxy-target-class="true"区别
    Spring MVC processing flow
    Spring中自动装配的模式
    Spring中BeanFactory和ApplicationContext的区别
    什么是Spring
  • 原文地址:https://www.cnblogs.com/ymd12103410/p/11136584.html
Copyright © 2011-2022 走看看