解决什么问题
举例:双向链表
已经有了shared_ptr
和unique_ptr
后,为什么还要提出weak_ptr
呢?以双向链表为例说明,各种引用关系如下图所示:
class Node{
public:
Node(){
cout << "call Node constructor" << endl;
}
~Node(){
cout << "call Node destructor" << endl;
}
int data;
shared_ptr<Node> pre;
shared_ptr<Node> next;
};
int main(){
shared_ptr<Node> sp1(new Node());
shared_ptr<Node> sp2(new Node());
// == 1
cout<<sp1.use_count()<<endl;
cout << sp2.use_count() << endl;
// ==
// == 2
// sp1 -> next = sp2;
// sp2 -> pre= sp1;
// cout<<sp1.use_count()<<endl;
// cout << sp2.use_count() << endl;
// ==
}
现象
在上述的代码中,只开启1对应的代码时,得到如下的输出:
当1和2都开启后,得到如下的输出:
发现当开启了2之后,析构函数没有被调用,说明出现了对象没有被销毁,出现了内存泄露。
原因
出现上述现象的原因在于,对于双向链表而言,当要释放node2内存空间时,需要指向node2的shared_ptr的reference count为0,为了实现这个目的,需要将node1.next释放,也就需要释放node1。
进一步,如果要释放node1,需要node2.pre被释放,也就需要释放node2。最后变成了:
- 要释放node2,先要释放node1;
- 要释放node1,先要释放node2;
即循环引用的问题。
weak_ptr的解决方式
只需要将Node中的next和pre变为weak_ptr
即可,其余仍然不变。
class Node{
public:
Node(){
cout << "call Node constructor" << endl;
}
~Node(){
cout << "call Node destructor" << endl;
}
int data;
weak_ptr<Node> pre;
weak_ptr<Node> next;
};
得到如下的输出:
发现能够成功调用到析构函数,说明内存被正确的释放了。
其中的原因在于,此时在双向链表中,node1和node2对应的shared_ptr的reference count均为1,没有形成环,也就没有循环引用的问题,可以自由释放。
特点
weak_ptr
具有如下的特点:
不控制所指向对象的lifetime,也不负责释放该对象;
do not control the lifetime of the object to which it points.
- 所指向的对象由
shared_ptr
负责管理。
- 所指向的对象由
由于不负责管理该对象,因此其不会改变
shared_ptr
的reference count;因为不会改变object的引用计数,也不负责其的释放,因此当shared_ptr的引用计数为0后,即便存在
weak_ptr
指向该object,也会毫不犹豫的执行object的释放。
因此,总结来看,weak_ptr
不负责、不管理所指向的object,只是一个“旁观者”。
初始化
weak_ptr
的初始化要依赖shared_ptr
:
auto p = make_shared<int>(42);
// wp weakly shares with p; use count in p is unchanged
// wp and p point to the same object
weak_ptr<int> wp(p);
确保object存在
因为当shared_ptr
所指向的对象被删除后,weak_ptr
也无效了,不能通过指针访问该对象了。因此,为了能够使得weak_ptr
有效地访问该对象,必须进行判定。
if (shared_ptr<int> np = wp.lock()) { //
// inside the if, np shares its object with p
}
lock
returns a shared_ptr to the shared object.
只要shared_ptr
仍然存在,其所指向的对象也就仍然存在。
相关操作
weak_ptr
的相关操作如下图所示:
参考资料
- C++ Primer - 12.1.6 weak_ptr