【问题标题】:Equality-compare std::weak_ptr相等比较 std::weak_ptr
【发布时间】:2012-08-31 09:09:39
【问题描述】:

我想比较两个 std::weak_ptr 或一个 std::weak_ptr 和一个 std::shared_ptr 是否相等。

我想知道的是每个weak_ptr/shared_ptr指向的对象是否相同。 不仅如果地址不匹配,而且如果底层对象被删除然后偶然用相同的地址重建,则比较应该会产生负面结果。

所以基本上,即使分配器保留相同的地址,我也希望这个断言成立:

auto s1 = std::make_shared<int>(43);
std::weak_ptr<int> w1(s1);

s1.reset();

auto s2 = std::make_shared<int>(41);
std::weak_ptr<int> w2(s2);

assert(!equals(w1,w2));

weak_ptr 模板不提供相等运算符,据我了解,这是for a good reason

所以一个简单的实现应该是这样的:

template <typename T, typename U>
inline bool naive_equals(const std::weak_ptr<T>& t, const std::weak_ptr<U>& u)
{
    return !t.expired() && t.lock() == u.lock();
}

template <typename T, typename U>
inline bool naive_equals(const std::weak_ptr<T>& t, const std::shared_ptr<U>& u)
{
    return !t.expired() && t.lock() == u;
}

如果第一个weak_ptr同时过期,它产生0。如果没有,我将weak_ptr升级为shared_ptr并比较地址。

这个问题是我必须锁定weak_ptr 两次(一次)!恐怕这需要太多时间。

我想出了这个:

template <typename T, typename U>
inline bool equals(const std::weak_ptr<T>& t, const std::weak_ptr<U>& u)
{
    return !t.owner_before(u) && !u.owner_before(t);
}


template <typename T, typename U>
inline bool equals(const std::weak_ptr<T>& t, const std::shared_ptr<U>& u)
{
    return !t.owner_before(u) && !u.owner_before(t);
}

它检查 u 的所有者块是否不在 t 的“之前”并且 t 不在 u 之前,所以 t == u。

这是否如我所愿?从不同的 shared_ptr 创建的两个weak_ptr 是否总是以这种方式比较不相等? 还是我错过了什么?

编辑:我为什么要首先这样做? 我想要一个带有共享指针的容器,并且我想分发对其中对象的引用。 我不能使用迭代器,因为它们可能会失效。我可以分发(整数)ID,但这会导致唯一性问题,并且需要地图类型和复杂的搜索/插入/删除操作。 这个想法是使用 std::set 并将指针本身(封装在包装类中)作为键,以便客户端可以使用weak_ptr 访问集合中的对象。

【问题讨论】:

  • “据我所知,这是有充分理由的。”如果你明白这是有充分理由的,那你为什么想要这样做呢?
  • 我刚刚在并发应用程序中遇到了这个问题,好东西owner_before() 存在。对于我的用例,比较控制块是唯一明智的答案。

标签: c++ c++11 shared-ptr weak-ptr


【解决方案1】:

完全重写这个答案,因为我完全误解了。这是一件很难做到的事情!

与标准一致的std::weak_ptrstd::shared_ptr 的通常实现是有两个堆对象:托管对象和一个控制块。每个指向同一个对象的共享指针都包含一个指向该对象和控制块的指针,每个弱指针也是如此。控制块记录共享指针的个数和弱指针的个数,当共享指针的个数达到0时释放被管理对象;当弱指针的数量也达到0时,控制块本身被释放。

由于共享或弱指针中的对象指针可以指向实际托管对象的子对象,例如一个基类、一个成员,甚至是托管对象拥有的另一个堆对象。

S0 ----------______       MO <------+
   \__             `----> BC        |
      \_ _______--------> m1        |
     ___X__               m2 --> H  |
S1 -/      \__ __----------------^  |
    \___ _____X__                   |
    ____X________\__                |
W0 /----------------`---> CB -------+  
                          s = 2 
                          w = 1 

这里我们有两个共享指针,分别指向托管对象的基类和成员,以及指向托管对象拥有的堆对象的弱指针;控制块记录了存在两个共享指针和一个弱指针。控制块还有一个指向托管对象的指针,当托管对象过期时,它使用它来删除托管对象。

owner_before/owner_less语义是通过控制块的地址来比较共享指针和弱指针,保证不改变,除非指针本身被修改;即使弱指针过期,因为所有共享指针都已被破坏,它的控制块仍然存在,直到所有弱指针也被破坏。

所以您的equals 代码绝对正确且线程安全。

问题在于它与shared_ptr::operator== 不一致,因为它比较对象指针,并且具有相同控制块的两个共享指针可以指向不同的对象(如上)。

为了与shared_ptr::operator== 保持一致,写t.lock() == u 绝对没问题;但是请注意,如果它返回true,那么仍然不能确定弱指针是另一个共享指针的弱指针;它可能是一个别名指针,因此在下面的代码中仍然可能过期。

但是,比较控制块的开销较小(因为它不需要查看控制块),并且如果您不使用别名指针,将得到与 == 相同的结果。


我认为这里的标准存在缺陷;添加owner_equalsowner_hash 将允许在无序容器中使用weak_ptr,并且给定owner_equals 实际上比较弱指针是否相等是明智的,因为您可以安全地比较控制块指针then em> 对象指针,因为如果两个弱指针具有相同的控制块,那么你就知道要么都过期,要么都不过期。可能是下一个版本的标准。

【讨论】:

  • 谢谢,我完全忘记了多线程,尽管我的应用程序肯定需要考虑它。我真正感兴趣的是与 shared_ptr 进行比较。 (w.lock() == s) 是否足以作为比较? w.lock() 是原子的,一旦它被锁定,没有一个指针不能过期。这行得通吗?
  • 感谢您的精心回复。我认为检查控制块地址的相等性应该足够有效。由于 owner_before() 对 shared_ptr 有重载,所以我可以使用后一个版本进行两个比较。
  • 顺便说一句,Microsoft guy 通过在控制块。还可以查看slides,在他的第 4 张幻灯片上,有一张“通常”实现的简洁图表。
  • 谢谢,下午好! we-know-where-you-live 优化不能总是被使用,因为它仍然可以构造一个shared_ptr 到一个newed 指针;在新代码中不正确,应该使用make_shared,但对于增加现有遗留代码和接口的安全性很有用。
  • 我完全同意owner_hashowner_equals。我认为这样的事情可能会有所帮助:template &lt;typename T&gt; class comparable_weak_ptr { const T* m_original; std::weak_ptr&lt;T&gt; m_weak; public: comparable_weak_ptr(std::shared_ptr&lt;T&gt; shr) : m_original(shr.get()), m_weak(std::move(shr)) {} bool operator==(const std::shared_ptr&lt;T&gt;&amp; shr) const { return (shr.get() == nullptr &amp;&amp; m_original == nullptr) || shr.get() == m_original &amp;&amp; shr == m_weak.lock(); } };
猜你喜欢
  • 1970-01-01
  • 2015-03-13
  • 2011-04-07
  • 2022-01-05
  • 1970-01-01
  • 2013-08-16
  • 2014-12-17
  • 1970-01-01
相关资源
最近更新 更多