【问题标题】:Another type of smart ptr, like unique_ptr with weak refs?另一种类型的智能指针,例如带有弱引用的 unique_ptr?
【发布时间】:2020-05-12 17:22:40
【问题描述】:

我最近遇到了一个问题,unique_ptrshared_ptr 似乎都不是正确的解决方案。 所以,我正在考虑发明另一种智能指针(如下所述),但我心想“我肯定不是第一个想要这个的人。”

所以我的高级问题是:

  • 下面的设计有意义吗?
  • 有没有办法通过现有的智能 ptrs(或其他 std:: 功能)来实现这一点,也许我错过了什么?

要求:

  • 我想要单一所有权,就像unique_ptr
    • 即:只有当单个拥有指针死亡时,才应释放底层对象(与 shared_ptr 的行为不同)。
  • 我想要一些额外的方法来引用在对象被删除时“感知”的对象。因此,类似于weak_ptr,但要与单一所有权模型一起使用。
  • 我确实需要线程安全

激励示例:

假设我正在迭代一个接口指针列表,并在它们上调用方法。 其中一些方法可能会导致列表中后面的项目被删除。

使用普通指针,我会得到那些已删除项目的悬空引用。

建议的设计:

让我们调用拥有指针my_ptr 和非拥有引用my_weak_ptr

对于给定的对象,我们可能有这样的图表:

                             _______
my_ptr<Obj> owner ---------> |Obj* | -------> [Obj data ... ]
                      +----> |count|
                      | +--> |_____|
my_weak_ptr<Obj> A ---+ |
                        |
my_weak_ptr<Obj> B -----+

my_ptr 的接口与unique_ptr 基本相同。 在内部,它将存储一个指向“控制块”的指针,该指针实际上只是“真实”指针和控制块本身的引用计数。 在销毁时,my_ptr 会将控制块指针设置为 NULL 并减少 refcount(并在适当的情况下删除控制块)。

my_weak_ptr 是可复制的,并且有一些get() 方法可以返回真正的Obj*。 用户将负责在使用它之前检查它是否为 NULL。 在销毁时,my_weak_ptr 会减少计数(并删除控制块,如果合适的话)。

缺点是每次访问都会在内存中进行两次跳跃。 对于my_ptr,也可以通过在内部存储真正的Obj* 来缓解这种情况,但my_weak_ptr 引用总是需要支付双跳成本。


编辑:一些相关问题,来自给出的链接:

所以似乎有这样的需求,但没有灌篮解决方案。如果需要线程安全,shared_ptrweak_ptr 是正确的选择,但如果不需要,它们会增加不必要的开销。

还有boost::local_scoped_ptr,不过还是共享所有权模式;我宁愿阻止拥有指针的副本,例如unique_ptr

【问题讨论】:

  • 您将如何为您的班级实施weak_ptr::lock?为什么不直接使用shared_ptrweak_ptr?重新发明轮子有什么好处?
  • @WernerHenze 如果你不需要线程安全,那么你就不需要lock。您只需检查对象是否还活着,然后在其他任何事情发生之前使用它。
  • 你能不能只使用指向my_ptr的普通指针并在使用之前查询它是否仍然指向任何东西?
  • get() 非常糟糕,因为 c++ 是并发的,并且在您执行 get() 之后,对象可能会被销毁。你不应该在 c++ 中访问你不拥有的指针(即使是暂时的)
  • @jwd 其他线程可以做的任何事情,您从线程调用的函数都可以做。如果您只调用不会导致对象被破坏的函数,那么只要您的代码正在运行,您就可以延长它的寿命。这就是共享所有权的定义,你说你不想要。但您实际上确实想要共享所有权。只要您使用get 返回的指针,您就需要保证该对象不会消失。无论您如何获得该保证,这就是共享所有权。所以只需使用shared_ptr

标签: c++ c++14 smart-pointers


【解决方案1】:

上面的cmets有一些很好的讨论,所以我将尝试回答我自己的问题并总结一下:

首先,整个概念有一个总体缺点:my_weak_ptr 的任何用户都需要非常小心,不要调用某些可能导致底层对象被删除的函数。或者如果他们这样做,他们需要重新检查弱 ptr 是否为空。这是放置在用户身上的未强制(和未强制)约束,就像他们使用原始指针一样。

话虽如此:这不是新领域。在随后的研究中,我发现了这种想法的各种化身:

  • VISH StrongPtr/WeakPtr
    • 非常合身。请注意,WeakPtr 没有 lock() 方法,并且文档说“如果引用的对象从其他地方被销毁,弱指针会神奇地变为空。”
    • 但是,它仍然是可复制的,因此不表示唯一的所有权。
  • Loki StrongPtr
    • 通过适当使用令人眼花缭乱的策略选择(或者可能是自定义策略),我认为我所描述的可以实现。
  • trackable_ptr
    • 另一种方法,因为您的 T 必须包装为 trackable&lt;T&gt;,但类似的问题正在解决。

还有一些更接近std的“相当不错但不太理想”的解决方案:

  • 使用shared_ptrweak_ptr
    • 缺点:线程安全开销、可复制的所有者 ptr、多个引用计数。
  • boost::local_shared_ptr,与weak_ptr 兼容。
    • 缺点:可复制的所有者 ptr、多个引用计数。

local_shared_ptr 可能是最好的开箱即用解决方案,质量高,缺点少。

但是,要真正挤出最后几个字节并禁止复制,就需要自定义解决方案。

除此之外,更哲学:

我从这里的讨论和其他阅读中了解到,许多人相信所有权的二元方法:要么它是共享的(所以使用shared_ptr,这也让你通过weak_ptr 共享观察)或者它是独一无二的(所以使用unique_ptr)。

这可能涵盖了 90% 以上的案例。然而,我想要具有共同观察的独特所有权(这是我的措辞;您可能会根据您的语义使用不同的词)。标准可能无法涵盖过于极端的情况,但我认为对于资源受限的系统来说,这似乎是一个合理的利基市场。

【讨论】:

    【解决方案2】:

    有一种设计不分配额外的块,而是使指针对象大小为 3 个指针。指针是双链表的一个节点,每个弱引用都是同一个链表的一个节点。

    缺点是线性删除复杂性(必须使每个引用无效),并且不可行,因此无法有效地实现线程安全。

    优点是共享指针和弱指针的快速解引用。

    我不记得我在哪里看到或听到过这个想法......

    【讨论】:

    • 啊,我认为这是我在研究中看到的“参考链接”,但不明白 - 谢谢你的解释(:(example link
    猜你喜欢
    • 1970-01-01
    • 2018-08-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-13
    • 1970-01-01
    • 2011-03-23
    相关资源
    最近更新 更多