C++11 智能指针——weak_ptr详解

和 shared_ptr、unique_ptr 类型指针一样,weak_ptr 智能指针也是以模板类的方式实现的。weak_ptr<T>( T 为指针所指数据的类型)定义在<memory>头文件,并位于 std 命名空间中。因此,要想使用 weak_ptr 类型指针,程序中应首先包含<memory>头文件。

需要注意的是,C++11标准虽然将 weak_ptr 定位为智能指针的一种,但该类型指针通常不单独使用(没有实际用处),只能和 shared_ptr 类型指针搭配使用。甚至于,我们可以将 weak_ptr 类型指针视为 shared_ptr 指针的一种辅助工具,借助 weak_ptr 类型指针, 可以获取 shared_ptr 指针的一些状态信息,比如有多少指向相同的 shared_ptr 指针、shared_ptr 指针指向的堆内存是否已经被释放等等。

此外,当 weak_ptr 类型指针的指向和某一 shared_ptr 指针相同时,weak_ptr 指针并不会使所指堆内存的引用计数加 1;同样,当 weak_ptr 指针被释放时,之前所指堆内存的引用计数也不会因此而减 1。也就是说,weak_ptr 类型指针并不会影响所指堆内存空间的引用计数。

同时,weak_ptr<T> 模板类中没有重载 * 和 -> 运算符,这也就意味着,weak_ptr 类型指针只能访问所指的堆内存,而无法修改它。

一、weak_ptr的作用

weak_ptr主要针对shared_ptr的空悬指针循环引用问题而提出:

(1)空悬指针问题:有两个指针p1和p2,指向堆上的同一个对象Object,p1和p2位于不同的线程中。假设线程A通过p1指针将对象销毁了(尽管把p1置为了NULL),那p2就成了空悬指针。

weak_ptr不控制对象的生命期,但是它知道对象是否还活着。如果对象还活着,那么它可以提升为有效的shared_ptr(提升操作通过lock()函数获取所管理对象的强引用指针);如果对象已经死了,提升会失败,返回一个空的shared_ptr。

(2)循环引用问题:

#include <iostream>
#include <memory>

using namespace std;

class Parent;
class Child; 

typedef shared_ptr<Parent> parent_ptr;
typedef shared_ptr<Child> child_ptr; 

class Parent
{
public:
       ~Parent() { 
              cout << "~Parent()" << endl; 
       }
public:
       child_ptr children;
};

class Child
{
public:
       ~Child() { 
              cout << "~Child()" << endl; 
       }
public:
       parent_ptr parent;
};

int main()
{
  parent_ptr father(new Parent);
  child_ptr son(new Child);

  // 父子互相引用
  father->children = son;
  son->parent = father;

  cout << father.use_count() << endl;  // 引用计数为2
  cout << son.use_count() << endl;     // 引用计数为2

  return 0;
}

如上代码,将在程序退出前,father的引用计数为2,son的计数也为2,退出时,shared_ptr所作操作就是简单的将计数减1,如果为0则释放,显然,这个情况下,引用计数不为0,于是造成father和son所指向的内存得不到释放,导致内存泄露。

使用weak_ptr可以打破这样的循环引用。由于弱引用不更改引用计数,类似普通指针,只要把循环引用的一方使用弱引用,即可解除循环引用。以上述代码为例,只要把Child类的代码修改为如下即可:

class Child
{
public:
       ~Child() { 
              cout << "~Child()" << endl; 
       }
public:
       weak_ptr<Parent> parent; 
};

最后值得一提的是,虽然通过弱引用指针可以有效的解除循环引用,但这种方式必须在能预见会出现循环引用的情况下才能使用,即这个仅仅是一种编译期的解决方案,如果程序在运行过程中出现了循环引用,还是会造成内存泄漏的。因此,不要认为只要使用了智能指针便能杜绝内存泄漏。

二、weak_ptr的创建

创建一个 weak_ptr 指针,有以下 3 种方式:

(1)创建一个空 weak_ptr 指针:std::weak_ptr<int> wp1;

(2)用已有的 weak_ptr 指针创建一个新的 weak_ptr 指针:weak_ptr<int> wp2 (wp1);

若 wp1 为空指针,则 wp2 也为空指针;反之,如果 wp1 指向某一 shared_ptr 指针拥有的堆内存,则 wp2 也指向该块存储空间(可以访问,但无所有权)。

(3)weak_ptr 指针更常用于指向某一 shared_ptr 指针拥有的堆内存,因为在构建 weak_ptr 指针对象时,可以利用已有的 shared_ptr 指针为其初始化。例如:

  1)shared_ptr<int> sp (new int);

  2)weak_ptr<int> wp3 (sp);

由此,wp3 指针和 sp 指针有相同的指针。再次强调,weak_ptr 类型指针不会导致堆内存空间的引用计数增加或减少。

三、weak_ptr模板类提供的成员方法

和 shared_ptr<T>、unique_ptr<T> 相比,weak_ptr<T> 模板类提供的成员方法不多,以下是常用的成员方法及各自的功能:

  1)operator=():重载 = 赋值运算符,是的 weak_ptr 指针可以直接被 weak_ptr 或者 shared_ptr 类型指针赋值。
  2)swap(x):其中 x 表示一个同类型的 weak_ptr 类型指针,该函数可以互换 2 个同类型 weak_ptr 指针的内容。
  3)reset():将当前 weak_ptr 指针置为空指针。
  4)use_count():查看指向和当前 weak_ptr 指针相同的 shared_ptr 指针的数量。
  5)expired():判断当前 weak_ptr 指针为否过期(指针为空,或者指向的堆内存已经被释放)。
  6)lock():如果当前 weak_ptr 已经过期,则该函数会返回一个空的 shared_ptr 指针;反之,该函数返回一个和当前 weak_ptr 指向相同的 shared_ptr 指针。

posted @ 2021-07-25 16:52  Jcpeng_std  阅读(1872)  评论(0编辑  收藏  举报