为什么在C++11中需要使用weak_ptr?

7
我正在阅读Nicolai M. Josuttis的《C++标准库》一书,以了解弱指针。作者提到需要使用weak_ptr的两个原因,我不太理解第二个原因。有没有人能够提供一个简单的解释,并且附带下面引用自书中的例子:
“另一个例子出现在您明确想要共享但不拥有对象时。因此,您具有引用对象的生命周期超过所引用的对象的语义。在这种情况下,shared_ptrs永远不会释放该对象,而普通指针可能无法注意到它们所引用的对象已不再有效,从而引入访问已释放数据的风险。”

4
想象一下,有一个共享所有权的对象缓存。你不想让这些对象无谓地存在,但只要它们确实还活着,缓存就可以让你找到这些对象并进行操作。 - Kerrek SB
这个链接可能对你有帮助。 - al0011
其实,也许知道书中给出的弱指针的第一个原因会有所帮助——我猜它是缓存,但也许不是。 - davidbak
第一个原因相当简单。(再次引用书中的话): "如果两个对象使用shared_ptr相互引用,并且您想在不存在对这些对象的其他引用时释放对象及其关联资源,则shared_ptr不会释放数据,因为每个对象的use_count()仍为1。在这种情况下,您可能需要使用普通指针,但这样做需要明确关注和管理相关资源的释放"。 - Tushar Jadhav
好的,第一个原因是为了打破循环依赖。虽然通常情况下你也不需要它们,因为循环中的一个对象具有控制另一个对象生命周期的功能。(下面关于事件源/接收器的答案在评论中讨论了这一点。)正如@KerrekSB所指出的那样,这个例子是针对缓存使用情况等其他情况的。 - davidbak
3个回答

10
那个语句的后半部分应该很清楚:如果指针不是拥有指针,那么它指向的对象可能会被任何拥有者软件删除 - 然后你将面临标准的悬空引用问题。因此,这个问题是:你有一些被某些软件拥有的对象,让其他软件可以访问它,但是其他软件不会共享拥有权。因此,所有者随时可以删除它,并且其他软件需要知道其指针不再有效。举个例子:你有一些软件正在观察窗户外的鸟食器,识别到进出的鸟儿。每只在鸟食器上停留的鸟都会由这个软件创建一个对象,并在鸟飞走时删除该对象。 同时,其他一些软件正在进行人口普查。每10秒钟,它从观鸟软件中获取一些鸟儿的集合。每100秒钟,它会发布一份报告,报告在整个100秒钟内哪些鸟儿一直在鸟食器上。由于一个鸟儿的数据很大,所以人口普查程序不会复制数据。它仅仅是从观鸟软件中获取每10秒钟一次的指针集合。为了使用弱指针,假设观鸟软件只提供最近10秒钟到达的鸟的指针,而不是一直在那里的鸟的指针。也就是说,没有通知哪些鸟已经消失了。通过使用弱指针,人口普查程序可以在报告时间知道哪些鸟仍然在那里以及它们何时到达(但不知道它们何时离开)。

7

e.g.:

struct node
{
   std::shared_ptr<node> left_child;
   std::shared_ptr<node> right_child;
   std::weak_ptr<node> parent;
   foo data;   
};

在这个例子中,删除节点将擦除左右孩子,但不会擦除父节点。如果由于某种原因节点比父节点存在更长的时间,并且删除了父节点,则可以知道父节点已经无效了。(假设您没有使用其他 shared_ptr 引用 left_child 或 right_child)

1
好的例子,不过关于“删除节点将擦除left_childright_child”- 对于unique_ptr来说是正确的;对于shared_ptr来说,它可能会擦除它们。 - Tony Delroy
1
嗯,是的,假设你只有一个共享指针指向那个东西的话,这是正确的。但是unique_ptr不支持weak_ptr。 - Exaeta

4
想象一下对一个想象中的定时器事件源的回调函数。
struct my_thing : std::enable_shared_from_this<my_thing>
{
  void start()
  {
    auto weak_self = std::weak_ptr<my_thing>(shared_from_this());
    _timer.set_callback([weak_self] {
      if (auto self = weak_self.lock()) {
        self->respond_to_timer();
      }
    });
  }

  void respond_to_timer()
  {
    // do something here
  }

  SomeTimer _timer;
};

在上面的例子中,my_thing 拥有计时器,但计时器被赋予了一个回调函数,该回调函数引用了 my_thing。这将会导致循环引用问题,防止 my_thing 被删除。
使用 weak_self 弱指针可以解决循环拥有问题。

如果my_thing实际上拥有计时器,为什么不直接使用普通指针来进行回调呢? - Kerrek SB
3
@KerrekSB 因为当事件处于计时器的调度程序中时,my_thing 可能已经被删除。锁定 weak_ptr 的操作证明委托对象存在,并在回调期间使其保持活动状态。在多线程解决方案中,这尤其重要。 - Richard Hodges
如果 my_thing 被删除了,那么包含的 _timer 也被删除了,是吗? - Kerrek SB
@KerrekSB:这里不会存在竞争条件吗?SomeTimer数据成员将在~my_thing的主体运行后的某个时刻“销毁”,但在~my_thing开始运行后调用计时器是不可取的。 - Tony Delroy
@KerrekSB 在很多情况下,事件源与事件分发机制是解耦的。这意味着事件可以在其预定代表的销毁后仍然存在。在任何事件消费者的生命周期比事件分发器(或总线)更短的系统中,您都会遇到这种情况。 - Richard Hodges

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接