循环引用导致内存泄露解决方案

循环引用:指的是多个对象相互引用时,使得引用形成一个环形,导致外部无法真正是否掉这块环形内存。其实有点类似死锁。
比如如我有一个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

猜你喜欢

转载自blog.csdn.net/ktigerhero3/article/details/80117359
今日推荐