C++弱引用智能指针weak_ptr的用处

weak_ptr也是一个引用计数型智能指针,但是它不增加对象的引用计数,即弱引用。与之相对,shared_ptr是强引用,只要有一个指向对象的shared_ptr存在,该对象就不会析构,直到指向对象的最后一个shared_ptr析构或reset()时才会被销毁。

利用weak_ptr,我们可以解决常见的空悬指针问题以及循环引用问题。

一、空悬指针问题

什么是空悬指针?考虑以下这种情况:
C++弱引用智能指针weak_ptr的用处
有两个指针p1和p2,指向堆上的同一个对象Object,p1和p2位于不同的线程中。假设线程A通过p1指针将对象销毁了(尽管把p1置为了NULL),那p2就成了空悬指针。这是一种典型的C/C++内存错误。

使用weak_ptr能够帮我们轻松解决上述的空悬指针问题。

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

#include <iostream>
#include <memory>

int main()
{
    // OLD, problem with dangling pointer
    // PROBLEM: ref will point to undefined data!

    int* ptr = new int(10);
    int* ref = ptr;
    delete ptr;

    // NEW
    // SOLUTION: check expired() or lock() to determine if pointer is valid

    // empty definition
    std::shared_ptr<int> sptr;

    // takes ownership of pointer
    sptr.reset(new int);
    *sptr = 10;

    // get pointer to data without taking ownership
    std::weak_ptr<int> weak1 = sptr;

    // deletes managed object, acquires new pointer
    sptr.reset(new int);
    *sptr = 5;

    // get pointer to new data without taking ownership
    std::weak_ptr<int> weak2 = sptr;

    // weak1 is expired!
    if(auto tmp = weak1.lock())
        std::cout << *tmp << '\n';
    else
        std::cout << "weak1 is expired\n";

    // weak2 points to new data (5)
    if(auto tmp = weak2.lock())
        std::cout << *tmp << '\n';
    else
        std::cout << "weak2 is expired\n";
}

二、循环引用问题

一种循环引用的情况如下:

#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可以打破这样的循环引用。由于弱引用不更改引用计数,类似普通指针,只要把循环引用的一方使用弱引用,即可解除循环引用。
C++弱引用智能指针weak_ptr的用处
以上述代码为例,只要把Child类的代码修改为如下即可:

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

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

上一篇:C++学习 四、智能指针总结


下一篇:Linux进程间通信之共享内存