【问题标题】:How can I enforce single ownership with weak_ptr? (Or, how to subclass shared_ptr and override dtor)如何使用weak_ptr 强制执行单一所有权? (或者,如何继承 shared_ptr 并覆盖 dtor)
【发布时间】:2013-08-30 05:48:44
【问题描述】:

所以,我已经完成了(少量)reading,并且知道unique_ptr 与原始指针结合使用是建模唯一所有权时使用的模式。

但是,我真的很喜欢使用 weak_ptr 来检查一个值是否有效,然后在使用后丢弃 shared_ptr 的简单明了的概念,这让每个人都很高兴(以轻微的引用计数性能成本为代价)。

我现在的特殊问题是创建一个富有表现力和灵活的系统来跟踪多点触摸点,使用代表触摸的对象的破坏作为触摸已经结束的信号似乎很优雅。如果我使用原始指针路由,我需要定义与该系统接口的每个组件都需要遵守的一些语义,这有点难看,比如涉及第二个参数,指示指针是否有效等。 原始指针路由的这个问题可能是一个稻草人问题,因为我不认为这会成为一个大型项目,但这个问题主要是关于如何编写最好的现代 C++ 代码。

伪代码:

class InputConsumer {
    void handle(std::list<std::weak_ptr<Touch>>*);
    // consumer doesnt hold references to anything outside of its concern. 
    // It only has to know how to deal with input data made available to it.
    // the consumer is a child who is given toys to play with and I am trying to
    // see how far I can go to sandbox it
}
class InputSender {
    std::list<std::weak_ptr<Touch>> exposedinputdata;
    std::list<std::shared_ptr<Touch>> therealownedtouches;
    // sender populates exposedinputdata when input events come in.
    // I want to let the consumer copy out weak_ptrs as much as it wants,
    // but for it to never hold on to it indefinitely. There does not appear
    // to be an easy way to enforce this (admittedly it is kind of vague. it
    // has to be around for long enough to be used to read out data, but
    // not e.g. 3 frames. Maybe what I need is to make an intelligent
    // smart pointer that has a timer inside of it.)
    std::list<std::weak_ptr<InputConsumer>> consumers;
    void feedConsumersWithInput() {
        for (auto i = consumers.begin(); i != consumers.end(); ++i) {
        if (i->expired()) {
            consumers.erase(i);
        } else {
            i->lock()->handle(&exposedinputdata);
        }
    }
}

当我看到weak_ptr 能够表达与我正在建模的语义非常相似的语义时,我真的很想使用它,因为它的界面简洁明了,最重要的是它自我记录了这段代码是怎样的去上班。这是一个巨大的好处。

现在我很确定在InputConsumerweak_ptr&lt;Touch&gt; 上调用lock() 并保留shared_ptr&lt;Touch&gt; 之前,一切都会非常顺利。即使在它的主要所有者删除了其拥有的shared_ptr 之后,它也会阻止底层Touch 被释放!在我看来,这似乎是唯一的皱纹,而且还有一点点。我认为用 shared_ptr 搞砸所有权处理要比用原始指针搞砸要难得多。

有什么方法可以解决这个问题?我正在考虑创建一个weak_ptr的模板子类(?!我从来没有写过这样的东西,最近进入了模板。爱他们)以某种方式禁止保留shared_ptr或其他东西。

也许我可以继承 shared_ptr 并覆盖它的 dtor 以抛出,如果它不调用删除器?

【问题讨论】:

  • 您可以轻松检查unique_ptr 是否包含有效指针:en.cppreference.com/w/cpp/memory/unique_ptr/operator_bool
  • 好吧,我不会检查 unique_ptr。系统的设计是这样的,它应该支持拥有 smartptr 的状态对正在执行检查的事物不可用。这就是为什么 weak_ptrs 这么好。我可以把它们像糖果一样传来传去,只要没有人去用糖果做不圣洁的事情,派对就会继续。不要没有人必须四处监视所有的人。 (糖果是在超空间中连接在一起的魔法信标)
  • 你说的lock()是什么?为什么调用它会阻止shared_ptr 删除其拥有的资源(假设引用计数为零)?
  • 有没有可能用一点伪代码来表达你的对象模型?我想你可能有一些错误的前提。通常,您可以在所有者对象中使用unique_ptr 成员和供其他人使用的原始指针。您只需要小心原始指针的生命周期嵌套在所有者对象的生命周期内。

标签: c++ pointers c++11 smart-pointers weak-references


【解决方案1】:

考虑到拥有weak_ptr 总是需要引用计数,推出任何解决方案(或多或少)就像重写shared_ptr 一样。

快速而肮脏的方法可能是派生 shared_ptr 并仅为其提供移动 ctor (monitore_ptr(monitored_ptr&amp;&amp;) ) 和传输运算符 (monitored_ptr&amp; operator=(monitored_ptr&amp;&amp;) ),从而禁用 shared_ptr 复制 (因此 "sharing") 能力。

派生的问题是,shared_ptr 不是多态的,你最终会得到一个非多态类型,它对 shared_ptr 表现出一些多态性(你可以分配给它,从而违反你的假设)。

这可以通过使用受保护的继承并仅重新公开所需的功能(主要是*-> 运算符)来弥补。

为了避免对weak_ptr 的错误行为(就像你的monitored_ptrweak_ptrshared_ptr)...我还建议用受保护的继承覆盖weak_ptr

此时,您最终会得到一对自给自足且与任何其他共享指针不兼容的类。

在任何情况下,关键是编写适当的构造函数,而不是(如您建议的那样)扔进析构函数:这是一种有很多潜在问题的情况,很难管理。

(参见例如here

【讨论】:

  • 这听起来很痛苦。我认为只要我不在接收端做一些愚蠢的事情,并且只以我迄今为止的方式使用weak_ptr(也就是说,从不保留shared_ptr,只在堆栈上创建它们以读出数据),那么一切都会顺利进行。
【解决方案2】:

我将提出一个非常简单的设计。它是一个围绕 weak_ptr 的薄包装器,其中访问底层 T 的唯一方法是将 lambda 传递给方法。

这将shared_ptr 的生命周期从lock() 限制为您调用该方法的时间:虽然理论上您可以无限期锁定shared_ptr,但您只能通过永不从try 返回来做到这一点.

template<typename T>
struct monitored_pointer {
  template<typename Lambda>
  bool try( Lambda&& closure ) const {
    auto p = m_ptr.lock();
    if (!p)
      return false;
    std::forward<Lambda>(closure)(*p):
    return true;
  }
  bool valid() const {
    return try( [](T&){} );
  }
  void reset( std::weak_ptr<T> ptr = std::weak_ptr<T>() )
  {
    m_ptr = ptr;
  }
  explicit operator bool() const { return valid(); }
  monitored_pointer() {}
  monitored_pointer( monitored_pointer && ) = default;
  monitored_pointer& operator=( monitored_pointer && ) = default;
  explicit monitored_pointer( std::weak_ptr<T> ptr ):m_ptr(ptr) {}
private:
  std::weak_ptr<T> m_ptr;
};

validoperator bool 仅在您想要清除过期的 monitored_pointers 时提供帮助。

使用看起来像:

if (!ptr.try( [&]( Touch& touch ) {
  // code that uses the `touch` here
})) {
  // code that handles the fact that ptr is no longer valid here
}

【讨论】:

    猜你喜欢
    • 2012-10-24
    • 1970-01-01
    • 1970-01-01
    • 2016-09-12
    • 2021-11-30
    • 2012-07-14
    • 1970-01-01
    • 2019-01-04
    相关资源
    最近更新 更多