【问题标题】:Why do we need a weak_ptr in C++11?为什么我们在 C++11 中需要一个 weak_ptr?
【发布时间】:2023-03-07 09:09:01
【问题描述】:

我正在阅读 Nicolai M. Josuttis 的“C++ 标准库”一书,以了解弱指针。作者提到了需要weak_ptr 的两个原因,而我没有得到第二个原因。任何人都可以提供一个简单的解释以及以下原因的示例(引自书中):

当您明确想要共享但不拥有对象时,会出现另一个示例。因此,您有这样的语义:对对象的引用的生命周期比它所引用的对象的寿命长。在这里,shared_ptrs 永远不会释放对象,普通指针可能不会注意到它们所引用的对象不再有效,这就带来了访问释放数据的风险。

【问题讨论】:

  • 想象一下使用共享所有权创建的对象缓存。您不想让对象不必要地保持活动状态,但只要它们实际上还活着,缓存就会为您提供一种获取对象句柄的方法。
  • 这个link可以帮助你
  • 实际上,了解本书给出的弱指针的第一个原因可能会有所帮助 - 我怀疑它是缓存,但也许不是。
  • 第一个原因相当简单。 (再次引用书中的内容):“如果两个对象使用 shared_ptrs 相互引用,并且如果不存在对这些对象的其他引用,您想要释放对象及其关联资源,shared_ptr 不会释放数据,因为 use_count每个对象的 () 仍然是 1。在这种情况下您可能希望使用普通指针,但这样做需要明确关心和管理关联资源的释放"
  • 好的,第一个原因是打破循环。尽管在这种情况下您通常也不需要它们,因为循环中的一个对象具有控制另一个对象的生命周期。 (下面关于事件源/接收器的答案在 cmets 中讨论了这一点。)正如@KerrekSB 指出的那样,此示例用于缓存用例等。

标签: c++ c++11


【解决方案1】:

该语句的后半部分应该很清楚:如果一个指针不是一个拥有指针,那么它所指向的对象可能会被任何软件删除所有者 - 然后您将获得标准的悬空参考。

所以这个问题是:您拥有某个软件拥有的对象,该软件允许其他软件访问它 - 但其他软件不会共享所有权。所以所有者可以随时删除它,其他软件需要知道它的指针不再有效。

也许举个例子会有所帮助:

您有一些软件正在监视一个摄像头,该摄像头将您的窗户指向喂鸟器,它正在识别喂食器上来来往往的鸟类。喂食器上的每只鸟在到达喂食器时都有一个由该软件创建的对象,当鸟儿飞走时该对象被删除。

同时,其他一些软件也在进行人口普查。每隔 10 秒,它就会从喂食器监视软件中抓取喂食器上的鸟类集合。每 100 秒它会发出一份报告,说明在整个 100 秒内哪些鸟在喂食器上。

因为鸟类的数据很大,所以人口普查员不会复制数据。它只是每 10 秒从 feeder-watcher 获取指针的集合。

为了有必要使用弱指针,假设 feeder-watcher 只提供指向最近十秒内到达的鸟类的指针,而不是那些已经到达那里的鸟类。也就是说,没有鸟消失的通知。

通过使用弱指针,它可以在报告时知道哪些鸟还在那里,以及它们何时到达(但不知道它们何时离开)。

(也许我以后会想一个更好的例子。)

【讨论】:

    【解决方案2】:

    例如:

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

    在本例中,删除节点将删除 left_child 和 right_child,但不会删除父节点。如果由于某种原因,节点比父节点停留的时间更长,并且父节点被删除,那么您就有办法知道父节点不再有效。 (假设您没有使用另一个 shared_ptr 引用 left_child 或 right_child)

    【讨论】:

    • 很好的例子,虽然关于 "删除节点将删除 left_childright_child" - 这对于 unique_ptr 是正确的;对于shared_ptrs,它可能删除它们。
    • 嗯,是的,假设您只有一个指向该事物的共享指针,这是正确的。但是 unique_ptr 不支持weak_ptr。
    【解决方案3】:

    想象一个虚构的计时器事件源的回调函数

    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_selfweak_ptr 的使用打破了循环所有权问题。

    【讨论】:

    • 如果my_thing 确实拥有计时器,为什么不直接使用普通指针进行回调?
    • @KerrekSB 因为my_thing 可能在事件在计时器的调度程序中时已被删除。锁定weak_ptr 的动作证明了委托对象存在并在回调期间保持其存活。这在多线程解决方案中尤为重要。
    • 如果my_thing被删除了,那么包含的_timer也被删除了,不是吗?
    • @KerrekSB:不会有竞争条件吗? SomeTimer 数据成员将在 ~my_thing 的主体运行后的某个时间被“破坏”,但在 ~my_thing 开始运行后调用计时器是不可取的。
    • @KerrekSB 事件源通常与事件分派机制分离。该事件在其预期的委托被破坏后仍然存在。在任何事件消费者的生命周期比事件调度器(或总线)短的系统中,您都会遇到这种情况。
    猜你喜欢
    • 1970-01-01
    • 2021-02-15
    • 2010-09-20
    • 1970-01-01
    • 2019-06-09
    • 2011-03-01
    • 2013-12-03
    • 2010-10-02
    相关资源
    最近更新 更多