如何使用weak_ptr? How to Use Weak Pointer
如何使用weak_ptr?
使用shared_ptr能自动释放“不再被需要的对象”的资源,避免资源泄漏。但是有以下两种情况可能share_ptr无法正常运作
- 环式指向cyclic reference,如果两对象使用shared_ptr互相指向对方,而一旦不存在其他reference指向它们时,你想释放它们和相应资源时,shared_ptr不会释放数据,因为每个对象的use_count()仍是1,此时你或许会想使用普通的指针,但这样做却要自行管理是释放相应资源了。
- 还有一种是你明确想共享但不愿拥有某对象的情况下,你要的语义是:reference的寿命比其所指向对象的寿命更长,因此,shared_ptr绝不释放对象,而普通指针可能不会注意到他们指向的对象已经不再有效,导致“访问已被释放的对象”的风险。
解决方案
标准库提供了类weak_ptr,允许你“共享但不拥有”某对象(即可以访问对象数据,但是不能操作对象)。这个weak_ptr建立起一个shared_ptr,一旦最末尾一个拥有该对象的shared_pointer失去了拥有权,任何weak pointer都会自动成空。
因此,在默认构造函数和copy构造函数之外,weak_ptr只提供“接收一个shared_ptr”的构造函数。
你不能使用操作符*和->访问weak_ptr指向的对象,而是必须另外建立一个shared pointer。理由如下:
- 在weak pointer之外建立一个shared pointer可因此检查是否扔存在一个相应对象,如果不,操作会抛出异常或建立一个empty shared pointer(实际究竟哪种行为乃取决于你所执行的是哪一种操作)。
- 当指向的对象正被处理时,shared pointer无法被释放
基于以上理由,weak_ptr只提供小量操作,只够用来创建、复制、赋值weak pointer。以及转换为一个shared pointer,或检查自己是否指向某对象。
weak_ptr使用示例
首先是使用shared_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<shared_ptr<Person>> kids;
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));
mom->kids.push_back(kid);
dad->kids.push_back(kid);
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]->name << endl;
p = initFamily("jim");
cout << "jim's family exists" << endl;
}
如上,p是指向上述家庭的最末一个handle。而在内部,每个person对象都有着reference从kid指向其父母以及反向指向。因此,在p被赋值之前,nico被共享三次,现在,如果我们释放手上最末一个指向该家庭的handle——也许是对p指派一个新person或一个nullptr,也许是main()结束时离开了p作用域——没有任何person会被释放,因为它们都至少被一个shared pointer指向,于是每个person的析构函数从未被调用:
nico's family exists
- nico is shared 3 times
- name of 1st kid of nico's mom: nico
jim's family exists
现在使用weak_ptr代替其中的部分shared_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_ptr
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;
// weak_ptr改变访问方式,使用lock()函数
cout << "- name of 1st kid of nico's mom: " << p->mother->kids[0].lock()->name << endl;
p = initFamily("jim");
cout << "jim's family exists" << endl;
}
这样处理后,使得在一个方向上(从kid到parent)用的是shared pointer,而从parent到kids则使用weak pointer
经过改造后,程序输出如下:
nico's family exists
- nico is shared 1 times
- name of 1st kid of nico's mom: nico
delete nico
delete nico's dad
delete nico's mom
jim's family exists
delete jim
delete jim's dad
delete jim's mom
注意:使用weak pointer时,调用方式改成p->mother->kids[0].lock()->name
这会导致新产生一个来自于kids容器内含之weak_ptr的shared_ptr,如果无法进行这样的改动——例如由于对象的最末拥有者也在此时释放了对象——lock()会产生一个empty shared_ptr。这种情况下调用*或->操作符会引发不明确行为。
如果不确定隐身于weak pointer背后的对象是否仍然存活,你有以下几个选择:
- 1.调用expired(),它会在weak_ptr不再共享对象时返回true。这等于检查use_count()是否为0,但速度较快。
-
- 可以使用相应的shared_ptr构造函数明确将weak_ptr转换为一个shared_ptr。如果被指对象已经不存在,该构造函数会抛出一个bad_weak_ptr异常,那是一个派生自std::exception的异常,其what()会产生“bad_weak_tr"
-
- 调用use_count(),询问相应对象的拥有者数量,如果返回0表示不存在任何有效对象,然而请注意,通常只应为了调式而调用use_count():c++标准库明确告诉我们:“use_count()并不总是很有效率”
举例:
try{
shared_ptr<string> sp(new string("hi"));
weak_ptr<string> wp = sp; // create weak pointer out of it
sp.reset(); // release object of shared pointer
count << wp.use_count() << endl; // prints: 0
cout << boolalpha << wp.expired() << endl; // prints: true
shared_ptr<string> p(wp); // throws std::bad_weak_ptr
}catch( const std::exception& e){
cerr << "exception: " << e.what() << endl; // prints: bad_weak_ptr
}