现在的位置: 首页 > 自动控制 > 工业·编程 > 正文

C++11智能指针(五):shared_ptr的循环引用的问题及weak_ptr

2019-04-23 22:24 工业·编程 ⁄ 共 3379字 ⁄ 字号 暂无评论

shared_ptr的主要优点是当不再使用时会自动释放相关的内存。

但是如果我们不仔细使用shared_ptr,那么这个优势就会变成一个劣势。 我们来看看:

假设我设计一个二叉树,并在其中包含一个指向左右子节点的指针。

#include <iostream>

#include <memory>

class Node {

  int value;

public:

  std::shared_ptr<Node> leftPtr;

  std::shared_ptr<Node> rightPtr;

  Node(int val) : value(val) {

    std::cout << "Constructor" << std::endl;

  }

  ~Node() {

    std::cout << "Destructor" << std::endl;

  }

};

int main() {

  std::shared_ptr<Node> ptr = std::make_shared<Node>(4);

  ptr->leftPtr = std::make_shared<Node>(2);

  ptr->rightPtr = std::make_shared<Node>(5);

  return 0;

}

上面的例子运行正常。

调用3次构造函数和3次析构函数。这意味着完整的内存被删除。

但是,如果我们添加另一个小的需求,即每个节点将包含一个指向父节点的指针。 那么它会导致shared_ptr的问题

查看修改过的代码

#include <iostream>

#include <memory>

class Node {

  int value;

public:

  std::shared_ptr<Node> leftPtr;

  std::shared_ptr<Node> rightPtr;

  std::shared_ptr<Node> parentPtr;

  Node(int val) : value(val) {

    std::cout << "Constructor" << std::endl;

  }

  ~Node() {

    std::cout << "Destructor" << std::endl;

  }

};

int main() {

  std::shared_ptr<Node> ptr = std::make_shared<Node>(4);

  ptr->leftPtr = std::make_shared<Node>(2);

  ptr->leftPtr->parentPtr = ptr;

  ptr->rightPtr = std::make_shared<Node>(5);

  ptr->rightPtr->parentPtr = ptr;

  std::cout << "ptr reference count = " << ptr.use_count() << std::endl;

  std::cout << "ptr->leftPtr reference count = " << ptr->leftPtr.use_count() << std::endl;

  std::cout << "ptr->rightPtr reference count = " << ptr->rightPtr.use_count() << std::endl;

  return 0;

}

输出:

Constructor

Constructor

Constructor

ptr reference count = 1

ptr->leftPtr reference count = 1

ptr->rightPtr reference count = 1

现在构造函数会被调用3次,但是不会调用析构函数,这意味着内存泄漏

导致这个shared_ptr问题的原因是循环引用,即:

如果两个对象使用shared_ptrs互相引用,那么当超出范围时,都不会删除内存。

发生这种情况是因为shared_ptr在其析构函数中递减关联内存检查的引用计数之后,如果count为0,则删除该内存,如果大于1,则意味着其他shared_ptr正在使用此内存。

但是在这种情况下,会发现这些shared_ptr在析构函数中count的值始终大于0。

让我们重新确认下上面的例子:

当ptr的析构函数被调用时,

·将引用计数减去1。

·然后检查当前计数是否为0,但是是2,因为左侧子元素和右侧子元素都具有引用父项的shared_ptr对象,即ptr。

·只有当ptr的内存被删除时,左右子节点才会被删除,但是由于引用计数大于0,这种情况不会发生。

·因此ptr和其子节点的内存都不会被删除。所以没有析构函数被调用。

那么,如何解决这个问题呢?

答案是使用weak_ptr

Weak_ptr允许共享,但不拥有一个对象。 它的对象是由shared_ptr创建的。

std::shared_ptr<int> ptr = std::make_shared<int>(4);

std::weak_ptr<int> weakPtr(ptr);weak_ptr<int>

对于weak_ptr对象,我们不能直接使用运算符*和 - >来访问关联的内存。首先,我们必须通过调用weak_ptr对象的的lock()函数来创建一个shared_ptr,这样只有我们可以使用它。

查看如下的例子

#include <iostream>

#include <memory>

int main() {

  std::shared_ptr<int> ptr = std::make_shared<int>(4);

  std::weak_ptr<int> weakPtr(ptr);

  std::shared_ptr<int> ptr_2 = weakPtr.lock();

  if (ptr_2)

    std::cout << (*ptr_2) << std::endl;

  std::cout << "Reference Count :: " << ptr_2.use_count() << std::endl;

  if (weakPtr.expired() == false)

    std::cout << "Not expired yet" << std::endl;

  return 0;

}

关键点:如果shared_ptr已经被删除,lock()会返回空的shared_ptr

使用weak_ptr改进我们的二叉树示例:

#include <iostream>

#include <memory>

class Node {

  int value;

public:

  std::shared_ptr<Node> leftPtr;

  std::shared_ptr<Node> rightPtr;

  //只需要把shared_ptr改为weak_ptr;

  std::weak_ptr<Node> parentPtr;

  Node(int val) : value(val) {

    std::cout << "Constructor" << std::endl;

  }

  ~Node() {

    std::cout << "Destructor" << std::endl;

  }

};

int main() {

  std::shared_ptr<Node> ptr = std::make_shared<Node>(4);

  ptr->leftPtr = std::make_shared<Node>(2);

  ptr->leftPtr->parentPtr = ptr;

  ptr->rightPtr = std::make_shared<Node>(5);

  ptr->rightPtr->parentPtr = ptr;

  std::cout << "ptr reference count = " << ptr.use_count() << std::endl;

  std::cout << "ptr->leftPtr reference count = " << ptr->leftPtr.use_count() << std::endl;

  std::cout << "ptr->rightPtr reference count = " << ptr->rightPtr.use_count() << std::endl;

  return 0;

}

输出:

Constructor

Constructor

Constructor

ptr reference count = 1

ptr->leftPtr reference count = 1

ptr->rightPtr reference count = 1

Destructor

Destructor

Destructor

给我留言

留言无头像?