测试代码如下:
代码中含有两个类,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也能正常析构。