凌云的博客

行胜于言

漫谈 C++ 智能指针 07

分类:c++| 发布时间:2015-11-09 23:17:00


循环引用

shared_ptr 适用于大部分的情况,但也有其局限性。 其中一个是不能正确处理循环引用的情况。

先来看一个例子:

#include <memory>
#include <vector>
#include <iostream>

using namespace std;

struct Person;

struct Team {
    Team() { cout << "Team constructor" << endl; }
    ~Team() { cout << "Team destructor" << endl; }
    vector<shared_ptr<Person> > persons;
};

struct Person {
    Person() { cout << "Person constructor" << endl; }
    ~Person() { cout << "Person destructor" << endl; }
    shared_ptr<Team> team;
};

int main()
{
    shared_ptr<Team> pt(new Team());
    shared_ptr<Person> pp(new Person());
    pt->persons.push_back(pp);
    pp->team = pt;
    return 0;
}
// vim: set et ts=4 sts=4 sw=4:

这里有两个类 Team 和 Person。 其中 Team 有个 persons 引用了 Person,而 Person 里面有一个 team 成员指向他所属的组。 然后来看看运行结果:

Team constructor
Person constructor

可以发现 Team 跟 Person 都没正常释放。 这是为什么呢? 由于 Team 和 Person 都有一个引用到对方的成员变量, 因此当执行了:

pt->persons.push_back(pp);
pp->team = pt;

pt 和 pp 的引用计数都是 2,当退出 main 函数后 pt 和 pp 析构,引用计数各减 1。 因此 pp 和 pt 都不会释放。

我们应该怎么解决这个问题呢? 一个比较直观的解决方法是将 Person 里的 team 变成原始指针。 当然这样又会引入其他的问题:当 Team 析构后,Person 的 team 就成了野指针了, 而我们没有办法知道他是一个已经无效的指针。

通过引入弱指针可以解决这个问题。

weak_ptr

std::weak_ptr 是一个保存由 std::shared_ptr 管理的对象的非拥有(弱)引用的智能指针。 在访问所引用的对象之前必须要先将其转换为 std::shared_ptr。

现在使用 std::weak_ptr 来改写上面的例子:

#include <memory>
#include <vector>
#include <iostream>

using namespace std;

struct Person;

struct Team {
    Team() { cout << "Team constructor" << endl; }
    ~Team() { cout << "Team destructor" << endl; }
    vector<shared_ptr<Person> > persons;
};

struct Person {
    Person() { cout << "Person constructor" << endl; }
    ~Person() { cout << "Person destructor" << endl; }
    weak_ptr<Team> team;
};

int main()
{
    shared_ptr<Team> pt(new Team());
    shared_ptr<Person> pp(new Person());
    pt->persons.push_back(pp);
    pp->team = pt;
    return 0;
}
// vim: set et ts=4 sts=4 sw=4:

输出为:

Team constructor
Person constructor
Team destructor
Person destructor

可以看到可以正确释放资源。

下面我们再来演示下如何通过 std::weak_ptr 访问所引用的对象。

#include <memory>
#include <vector>
#include <string>
#include <iostream>

using namespace std;

struct Person;

struct Team {
    Team() { cout << "Team constructor" << endl; }
    ~Team() { cout << "Team destructor" << endl; }
    vector<shared_ptr<Person> > persons;
    string name;
};

struct Person {
    Person() { cout << "Person constructor" << endl; }
    ~Person() { cout << "Person destructor" << endl; }
    weak_ptr<Team> team;
    string name;

    void show() {
        shared_ptr<Team> t = team.lock();
        if (!t) {
            cout << "Team not found" << endl;
            return;
        }

        cout << name << " in " << t->name << endl;
    }
};

int main()
{
    shared_ptr<Team> pt(new Team());
    shared_ptr<Person> pp(new Person());
    pt->name = "Test team";
    pt->persons.push_back(pp);
    pp->team = pt;
    pp->name = "Jack";
    pp->show();
    return 0;
}
// vim: set et ts=4 sts=4 sw=4:

输出为:

Team constructor
Person constructor
Jack in Test team
Team destructor
Person destructor

在这里我们先对 weak_ptr 使用 lock() 返回对应的 shared_ptr 然后检查 shared_ptr 是否有效(检查通过 weak_ptr 返回的 shared_ptr 是一个好习惯, 除非你能确保返回的 shared_ptr 是有效的。)然后输出我们需要的信息。