挖挖智能指针中的小透明-----weak_ptr以及auto_ptr

我们知道智能指针是C++引入解决“忘记释放申请的空间导致内存泄漏”的问题的,包括老一代的auto_ptr以及C++11新引入的shared_ptr,unique_ptr和weak_ptr。

对于unique_ptr和shared_ptr,作为功能强大的两款常规智能指针,unique_ptr限制同一时间只能有一个指针指向对象,并且可以很好的解决auto_ptr的缺陷。而shared_ptr则允许有多个指针指向对象,通过计数器(use_count())的方式判断空间是否该释放,当调用release(),当前指针便会释放控制权,计数器减一,当计数器置为0时,空间释放。

这里主要说说另外两个智能指针中的小透明。

首先对于已经被弃用(被unique_ptr取代)的auto_ptr,他被取代的主要原因,也就是其重大缺陷在于赋值双方的所有权问题。比如如下代码:

auto_ptr< string> ps (new string ("auto_ptr is low");
auto_ptr<string> newp; 
newp = ps;

 

当执行完指针赋值这里后,赋值方ps就失去了对该处内存的控制权,而转交到了newp手中,并使自己置为空。这意味着对ps的打印会出现空串的情况,但编译不会报错。而如果时unique_ptr,在赋值这一步就会出现编译错误,因为unique_ptr不能被直接赋值。

其次,auto_ptr不能作为函数的参数以及返回值,并且不能被保存在容器当中,这是由于没有移动语义造成的,但在C++11之后有了移动语义的存在,使得unique_ptr更加优于auto_ptr。

而另一个小透明weak_ptr,它从实质上讲不能算是个智能指针,它并不能控制对象的生命周期,而是用来辅佐shared_ptr的。shared_ptr虽然功能强大,但其计数器系统却会带来别的问题,比如两个shared_ptr相互引用,就会导致计数器永远无法置为0,于是就要用到weak_ptr了。

weak_ptr 是一种不控制对象生命周期的智能指针,它指向一个 shared_ptr 管理的对象,进行该对象的内存管理的是那个强引用的 shared_ptr。weak_ptr只是提供了对管理对象的一个访问手段。weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作,它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造,它的构造和析构不会引起引用记数的增加或减少。它是对对象的一种弱引用,不会增加对象的引用计数,和shared_ptr之间可以相互转化,shared_ptr可以直接赋值给它,它可以通过调用lock函数来获得shared_ptr。

有以下代码:

#include <iostream>
#include <memory>

class B;
class A {
  public:
    A() {
      std::cout << "A()" << std::endl;
    }
    ~A() {
      std::cout << "~A()" << std::endl;
    }
    void set_ptr(std::shared_ptr<B>& ptr) {
      myptr_b = ptr;
    }
  private:
    std::shared_ptr<B> myptr_b;
};

class B {
  public:
    B() {
      std::cout << "B()" << std::endl;
    }
    ~B() {
      std::cout << "~B()" << std::endl;
    }
    void set_ptr(std::shared_ptr<A>& ptr) {
      myptr_a = ptr;
    }
  private:
    std::shared_ptr<A> myptr_a;
};

int main()
{
  std::shared_ptr<A> ptr_a(new A());
  std::shared_ptr<B> ptr_b(new B());
  ptr_a->set_ptr(ptr_b);
  ptr_b->set_ptr(ptr_a);
  std::cout << ptr_a.use_count() << " " << ptr_b.use_count() << std::endl;

  return 0;
}

 

可以看到这里我们实现了两个shared_ptr的相互引用,最后打印结果如下

A()
B()
2 2

 

可以看到析构函数没有被调用。为什么呢?

分析一下,起初定义完ptr_a和ptr_b时,ptr_a指向A对象,ptr_b指向B对象。然后调用函数set_ptr后又增加另外两条引用,即B对象中的myptr_a成员变量指向A对象,A对象中的myptr_b成员变量指向B对象。这个时候,指向A对象的有两个,指向B对象的也有两个。当main函数运行结束时,对象ptr_a和ptr_b被销毁,他们两指向A和B对象的关系也就结束,但是myptr_a和myptr_b对A和B的两条引用依然存在,每一个的引用计数都不为0,结果就导致其指向的内部对象无法析构,造成内存泄漏。

而weak_ptr的出现就是为了解决这个问题,方法是将其中一个shared_ptr换成weak_ptr,比如将B中的成员变量改为weak_ptr对象,代码如下:

class B {
  public:
    B() {
      std::cout << "B()" << std::endl;
    }
    ~B() {
      std::cout << "~B()" << std::endl;
    }
    void set_ptr(std::shared_ptr<A>& ptr) {
      myptr_a = ptr;
    }
  private:
    std::weak_ptr<A> m_ptr_a;

 

而此时,运行结果变为了

A()
B()
1 2
~A()
~B()

 

这里导致的底层改变是,B中的myptr_a指向A的关系由weak_ptr取代了,并不会增加引用计数。也就是说,A的对象只有一个引用计数,CB的对象只有2个引用计数,当main函数返回时,对象ptr_a和ptr_b被销毁,此时A对象的引用计数会减为0,对象被销毁,其内部的myptr_b成员变量也会被析构,导致B对象的引用计数会减为0,对象被销毁,进而解决了引用成环的问题。

上一篇:boost::enable_shared_from_this相关的测试程序


下一篇:Ubuntu18.04导入科大讯飞离线合成语音SDK