1. 程式人生 > 其它 >shared_ptr中迴圈引用問題

shared_ptr中迴圈引用問題

技術標籤:C/C++c++指標

測試程式碼如下:

程式碼中含有兩個類,Parent和Child。

Parent類成員中有一個Child類的智慧指標。

Child類成員中有一個Parent類的智慧指標。

#include <iostream>
#include <memory>
​
class Child;
typedef std::shared_ptr<Child> ChildPtr;
class Parent;
typedef std::shared_ptr<Parent> ParentPtr;
​
class Parent
{
public:
  Parent(){std::cout << "Parent hello\n";}
  ~Parent(){std::cout << "Parent bye\n";}
  void setSon(ChildPtr& c){son=c;}
private:
  ChildPtr son;
};
​
class Child
{
public:
  Child(){std::cout << "Child hello\n";}
  ~Child(){std::cout << "Child bye\n";}
  void setParent(ParentPtr& p){parent=p;}
private:
  ParentPtr parent;
};
​
void testParnentAndChild()
{
  ParentPtr p(new Parent());
  ChildPtr c(new Child());
  p->setSon(c);
  c->setParent(p);
}
​
int main()
{
  testParnentAndChild();
  return 0;
}

執行後得到如下結果:

顯而易見的是,testParnentAndChild()函式中的p和c物件構造成功,但是在離開該作用域時沒有呼叫Parent和Child類的解構函式。這與shared_ptr記憶體管理機制有所矛盾。

將testParnentAndChild()函式進行修改後在執行,能得到以下輸出

void testParnentAndChild()
{
  ParentPtr p(new Parent());
  ChildPtr c(new Child());
  std::cout << "Before set\n";
  std::cout << "p_useconut: " << p.use_count() << std::endl;
  std::cout << "c_useconut: " << c.use_count() << std::endl;
  p->setSon(c);
  c->setParent(p);
  std::cout << "After set\n";
  std::cout << "p_useconut: " << p.use_count() << std::endl;
  std::cout << "c_useconut: " << c.use_count() << std::endl;
}

由執行結果可以的看出,在進行set操作之後,p和c的引用計數分別+1,這是因為p中的成員p→son引用了c,c中成員c→parent引用了p,所以在離開函式作用域時,因為p和c失效了,即使p和c的引用計數-1,p和c的引用計數也不為0。所以無法呼叫解構函式(shared_ptr中引用計數為0時呼叫解構函式)。

解決方法:將其中一個物件的類成員修改為weak_ptr,打破迴圈引用。

#include <iostream>
#include <memory>
​
class Child;
typedef std::shared_ptr<Child> ChildPtr;
typedef std::weak_ptr<Child> ChildWeakPtr;
class Parent;
typedef std::shared_ptr<Parent> ParentPtr;
​
class Parent
{
public:
  Parent(){std::cout << "Parent hello\n";}
  ~Parent(){std::cout << "Parent bye\n";}
  void setSon(ChildPtr& c){son=c;}
private:
  ChildWeakPtr son;//修改為weak_ptr
};
​
class Child
{
public:
  Child(){std::cout << "Child hello\n";}
  ~Child(){std::cout << "Child bye\n";}
  void setParent(ParentPtr& p){parent=p;}
private:
  ParentPtr parent;
};
​
void testParnentAndChild()
{
  ParentPtr p(new Parent());
  ChildPtr c(new Child());
  std::cout << "Before set\n";
  std::cout << "p_useconut: " << p.use_count() << std::endl;
  std::cout << "c_useconut: " << c.use_count() << std::endl;
  p->setSon(c);
  c->setParent(p);
  std::cout << "After set\n";
  std::cout << "p_useconut: " << p.use_count() << std::endl;
  std::cout << "c_useconut: " << c.use_count() << std::endl;
}
​
int main()
{
  testParnentAndChild();
  return 0;
}

其執行結果為:

可以看到,離開testParnentAndChild()函式作用域時,p和c都成功的呼叫了其解構函式。觀察輸出結果可以發現,在set之後,c的usecount並沒有增加,這是因為使用weak_ptr時不會影響其引用計數(weak_ptr在外部引用計數不為0時有效)。

所以在離開testParnentAndChild()函式作用域時,一旦c能正常析構,c也就不會再引用p,打破了迴圈引用,保證p也能正常析構。