循环引用:指的是多个对象相互引用时,使得引用形成一个环形,导致外部无法真正是否掉这块环形内存。其实有点类似死锁。
比如如我有一个people类,在有一个car,people有一个car的属性,car类中又people的属性,此时产生循环引用问题.
1.循环引用导致内存永远不被清理例子
首先来看一个循环引用导致内存泄露的例子.
#include <iostream>
#include <string>
#include <vector>
#include <memory>
using namespace std;
class Person {
public:
string name;
Person* mother;
Person* father;
vector<Person*> kids;
Person (const string& n,
Person* m = nullptr,
Person* f = nullptr)
: name(n), mother(m), father(f) {
}
~Person() {
cout << "delete " << name << endl;
}
};
Person* initFamily (const string& name)
{
Person* mom(new Person(name+"'s mom"));
Person* dad(new Person(name+"'s dad"));
Person* kid(new Person(name,mom,dad));
mom->kids.push_back(kid);
dad->kids.push_back(kid);
return kid;
}
int main()
{
Person* p = initFamily("nico");
cout << "nico's family exists" << endl;
return 0;
}
此程序运行完后输出
nico’s family exists
由于没有手动调用析构函数,会出现内存泄露问题.
使用valgrind检测
valgrind --tool=memcheck --leak-check=full ./leaktest
definitely lost: 128 bytes in 2 blocks
证明确实有内存泄露.
3内存泄露原因分析
循环引用如下图所示,
如图,mon和dad引用了kid,kid引用了mon和dad.
当释放最后一个引用该家庭的指针时,家庭的每个成员至少被一个 pointer 指向.造成内存泄露.
3解决方法1:使用weak_ptr
循环引用解决原则是:
父引子强引用,子引父弱引用。就是避免两个强指针相互引用.
下面代码使用c++11中的智能指针weak_ptr解决循环引用.
#include <iostream>
#include <string>
#include <vector>
#include <memory>
using namespace std;
class Person {
public:
string name;
shared_ptr<Person> mother;
shared_ptr<Person> father;
vector<weak_ptr<Person>> kids; // weak pointer !!!
Person (const string& n,
shared_ptr<Person> m = nullptr,
shared_ptr<Person> f = nullptr)
: name(n), mother(m), father(f) {
}
~Person() {
cout << "delete " << name << endl;
}
};
shared_ptr<Person> initFamily (const string& name)
{
shared_ptr<Person> mom(new Person(name+"'s mom"));
shared_ptr<Person> dad(new Person(name+"'s dad"));
shared_ptr<Person> kid(new Person(name,mom,dad));
weak_ptr<Person> wkid(kid);
mom->kids.push_back(wkid);
dad->kids.push_back(wkid);
return kid;
}
int main()
{
shared_ptr<Person> p = initFamily("nico");
cout << "nico's family exists" << endl;
cout << "- nico is shared " << p.use_count() << " times" << endl;
cout << "- name of 1st kid of nico's mom: "
<< p->mother->kids[0].lock()->name << endl;
return 0;
}
使用weakptr后关系如图.
注意,程序的中p.use_count()输出为1.
c++ primer中如下描述weak_ptr:
将一个weak_ptr绑定到一个share_ptr 不会改变shared_ptr的引用计数.一旦最后一个指向对象的shared_ptr被销毁,对象被释放.即使weak_ptr指向对象,对象还是被释放.
以上程序initFamily时将一个weak_ptr wkid 绑定到share_ptr kid,不会改变shared_ptr kid的引用计数.当程序结束时,shared_ptr mom,dad,kid被销毁,对象被释放,即使mom和dad的weak_ptr指向对象,对象还是被释放.
4.解决方法2:由程序逻辑手动释放内存
在这个内存泄露的程序中,有三个对象,mom,dad,kid,他们相互引用.因此,依次手动释放他们分配的内存就能够避免内存泄露.
解决方法如下:
#include <iostream>
#include <string>
#include <vector>
#include <memory>
using namespace std;
class Person {
public:
string name;
Person* mother;
Person* father;
vector<Person*> kids;
Person (const string& n,
Person* m = nullptr,
Person* f = nullptr)
: name(n), mother(m), father(f) {
}
~Person() {
cout << "delete " << name << endl;
}
};
Person* initFamily (const string& name)
{
Person* mom(new Person(name+"'s mom"));
Person* dad(new Person(name+"'s dad"));
Person* kid(new Person(name,mom,dad));
mom->kids.push_back(kid);
dad->kids.push_back(kid);
return kid;
}
int main()
{
Person* p = initFamily("nico");
cout << "nico's family exists" << endl;
delete p->mother;
delete p->father;
delete p;
return 0;
}
参考:
The C++ Standard Library - A Tutorial and Reference, 2nd Edition