问题关键,并不是采用 shared_ptr 还是 weak_ptr,而是如何组织对象的生命周期管理。
从生命周期的管理上,shared_ptr 相当于做了一个 ReadLockGuard。在这个 Guard 析构之前,这个对象就一定是可用的,不会被 Free。也就保证了,在 Guard 范围内,能任意地使用 shared_ptr.get() 出来的指针。
但是,既然是 Lock,就需要控制 Lock 的范围。如果一直持有,就等于资源泄露;如果持有时间过长,会造成资源占用过久,浪费资源。更严重的是,如果有功能依赖对象析构,这个功能的执行可能阻塞过久。
weak_ptr 的作用,是用于辅助控制这个 Lock 的范围,让你有办法减少 Lock 的持有时间和范围。例如,下面一个简单的例子:
void long_hold(const shared_ptr& obj) {
shared_ptr holder = obj;
while(true) {
holder.DoSomething();
sleep(1);
}
}
void short_hold(const shard_ptr& obj) {
weak_ptr weak_holder = obj;
while(true) {
{
auto tmp_holder = weak_holder.lock();
if (!tmp_holder) break;
tmp_holder->DoSomething();
}
sleep(1);
}
}
上面实现了两个函数,long_hold () 在栈上持有了一个 shared_ptr,Lock 的范围是整个 long_hold 函数。在 long_hold 返回之前,对象不可能 Free。
short_hold() 在栈上持有的是 weakptr,这里并不会创建 Lock。而是等到 while 循环中不停地临时 Lock,使用完毕后立即释放。注意,与 long_hold 不同的是,这个 Lock 范围并不包含 sleep。那么,在 sleep 期间,外部的资源持有者就有机会执行 Free。
铺垫了这么多,下面说说我认为的一种适用范围广的实践姿势:参数类型使用 const shared_ptr & 或 A *。如果函数内可能传递共享权,用 const shared_ptr &;否则,用 A *;
必须在一个外部 shared_ptr (通常是在栈上)的保护下,传递 A* 或 const shared_ptr & 给函数做参数;
在调用函数中,根据目的,构造出 weak_ptr 或 shared_ptr 来传递共享权。
这样实践,一是,性能惩罚小,只在必需时构造 weak_ptr 和 shared_ptr;二是,参数类型能表明函数是否可能泄露共享权。