如何使用weak_ptr强制执行单一所有权?(或者,如何子类化shared_ptr并覆盖dtor)

How can I enforce single ownership with weak_ptr? (Or, how to subclass shared_ptr and override dtor)

本文关键字:ptr 子类 dtor 覆盖 shared 或者 强制执行 weak 何使用 单一 所有权      更新时间:2023-10-16

因此,我已经阅读了(少量),并意识到unique_ptr与原始指针相结合是建模唯一所有权时使用的模式。

然而,我真的很喜欢使用weak_ptr来检查一个值是否有效,然后在使用后丢弃shared_ptr这一简单明了的概念,这让每个人都很高兴(只需要稍微考虑一下性能成本)。

我现在的特别问题是创建一个富有表现力和灵活性的系统来跟踪多点触摸,使用代表触摸的对象的破坏作为触摸结束的信号似乎很优雅。如果我使用原始指针路由,我将需要定义与该系统接口的每个组件都需要遵守的一些语义,这有点难看,比如涉及第二个参数,指示指针是否有效或诸如此类。原始指针路由的这个问题可能是一个strawman问题,因为我不认为这会成为一个大项目,但这个问题主要是关于如何编写最好的现代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<Touch>上调用lock()并保留shared_ptr<Touch>之前,一切都会非常顺利。它将防止底层Touch被释放,即使它的主要所有者已经擦除了它所拥有的shared_ptr!在我看来,这是唯一的皱纹,也是一个小小的皱纹。我认为用shared_ptr搞砸所有权处理要比用原始指针搞砸困难得多。

有什么方法可以弥补这一点?我正在考虑制作一个weak_ptr的模板子类(?!我从来没有写过这样的东西,最近进入了模板。喜欢它们),它将以某种方式禁止保留shared_ptr或其他什么。

如果shared_ptr不调用deleter,也许我可以将其子类化并覆盖其dtor以抛出?

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

快速而肮脏的方法可能是派生shared_ptr,并仅为其提供move ctor(monitore_ptr(monitored_ptr&&))和transfer操作符(monitored_ptr& operator=(monitored_ptr&&)),从而禁用shared_ptr复制(因此禁用"共享")功能。

派生的问题是,由于shared_ptr不是多态的,您最终会得到一个非多态类型,它向shared_ptr提供了一些多态性(您可以分配给它,从而违反了您的假设)。

这可以通过使用受保护的继承来补偿,并只重新公开所需的功能(本质上是*->运算符)。

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

在这一点上,您最终得到了一对自给自足的类,并且与任何其他共享指针都不兼容。

在任何情况下,关键是编写合适的构造函数,而不是(正如你所建议的)抛出析构函数:这是一种有很多潜在问题的情况,很难控制。

(参见此处的示例)

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

这将shared_ptrlock()的生存期限制为您调用该方法的时间:虽然理论上您可以无限期地锁定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_pointer s时会有所帮助。

使用看起来像:

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