我应该在不主要是容器上使用 shared_ptr 还是 weak_ptr?

Should I use shared_ptr or weak_ptr on not mainly containers?

我有两个标准容器。它们都具有指向相同数据结构的指针。第一个包含所有数据,第二个仅包含部分相同数据。我应该在第二个容器上使用 shared_ptr 还是 weak_ptr

首先,当我阅读参考资料时,我想到了在第一个集合中使用 unique_ptr。我的第一个集合包含所有数据,并且是唯一一个 "owns"。这意味着如果数据不存在,则应将其删除。但是当我尝试创建第二个系列时,我不知道该怎么做。我创建了一个唯一指针,但现在我需要另一个指针指向同一个元素,破坏了唯一性,但实际上真正的所有者不是新指针。所以我明白(我希望我没有错)唯一性是在到达元素的路上而不是(例如)删除它的可能性。所以,shared_ptr。我把它们放在我的第一个系列中。但是现在第二个出现了,我想在这里也使用 shared_ptr 。访问相同数据的方式可能是两种,所以所有者是两个。但在我的例子中,数据总是从前一秒开始删除。如果我使用 weak_ptr ,所有者的数量不会增加。在这两种情况下,元素都将在第一个集合需要时被删除。最后我使用 shared_ptr 因为使用 weak_ptr 我需要 lock() 每行代码中的每个指针,使其可读性降低。但是我到底应该用什么?

"every line of code" 是什么意思?通常的模式如下:

if (auto p = wp.lock()) {
    // p is a shared_ptr; use it as often as you need within the block.
}

如果您有一个拥有容器和一个引用容器,并且在所有者破坏裁判时仍能正常工作,那么 shared_ptr/weak_ptr 是可行的方法。

好问题,

根据经验,我发现使用 shared_ptr/weak_ptr,甚至完全使用 shared_ptr,通常会导致某种大的模糊设计。

过去有人提倡 shared_ptr 可能被认为是有害的,就这样。因为它使所有权浮动。并且所有权在设计上应该是明确的。 我不确定我是否想自己采纳这个建议并提倡它;但为了回答问题,我肯定会在这里重复一遍。

另外,可以认为到处使用shared_ptr就和使用垃圾回收一样。它只是引用计数,但最终表现相同。只是性能不太好,过去已经证明,一个好的垃圾收集引擎比所有引用计数都快。 (这肯定是因为 shared_ptr 中需要 CPU 原子指令和障碍,但我推测。)

也许您应该考虑转向垃圾收集真正完善的 language/platform,例如 .NET 4 上的 C#?

否则,如何摆脱直接指针方案并使用标识符,您可以制作一个管理器模式,您的 2 个索引数据结构在此管理器中是私有的。客户只能通过经理的 API 查看和使用标识符(intuint64_t?由您自行决定)。

我发现这种方法的问题是需要在管理器中重复整个 API 被操纵的对象。这是痛苦的,不尊重DRY

否则,您是否考虑过您的数据结构可能并不是天生必需的?
我的意思是,无论如何存储指针时,索引数据结构往往不会那么大。它只是 N*sizeof(ptr_t) 而不是 N*sizeof(value_t)。这突然使 std::vector 在所有情况下都成为优秀的候选人。我的意思是向量已经是几乎所有用途的最佳数据结构,there are many advocates of this theory.

如果您的向量只包含指针,帮自己一个忙,通过使用 boost::ptr_vector 来减轻 shared_ptr 开销。

我希望我带来了一些观点。

听起来您不需要 std::shared_ptr,因为您的数据归于一处。

我建议在所属容器中使用 std::unique_ptr,然后简单地将 原始指针 放入第二个和后续容器中。

这是可行的,因为您永远不会删除原始指针,但它们指向的数据仍由智能指针管理,因此当您不再需要时将被释放。

尽管有一些负面报道,原始指针在用作非拥有访问器到其他实体拥有的数据时是完全值得尊敬的将在适当的时候删除它。

你没有给出一个关键的信息,就是你能不能保证两个集合的生命周期相同。如果您可以保证两个集合具有相同的生命周期,那么对拥有所有内容的集合使用 unique_ptr 并为另一个集合使用原始指针(如@Galik 建议的那样)是理想的。

如果你不能保证两个生命周期匹配,那么你是 select shared_ptr 还是第一个 shared_ptr 第二个弱取决于你什么时候希望对象被销毁。听起来只有第一个集合才是真正的所有者,因此您需要弱指针。

但是,我强烈建议您坚持使用第一种方法。避免 shared_ptr 和 weak_ptr 会干净得多。危险在于,如果您的两个集合具有不同的生命周期,则第一个集合可能会在第二个集合之前被销毁(只是一个错位的大括号),然后当第二个集合尝试访问时,它会出现悬挂指针。你当然可以简单地小心你的变量,但是保证两个独立的局部变量始终具有相同的生命周期是非常容易搞砸的。

如果你使两个集合元素相同class,你保证它们同时被构建和销毁。当然,class 应该是什么样子以及哪些代码应该放在哪里的确切细节取决于您的问题的细节。但即使是让它们成为同一结构的唯一成员(和 public)这样简单(尽管很奇怪)也比使用两个局部变量要好。