Equality-compare std :: weak_ptr

时间:2012-09-06 14:04:55

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

我想比较两个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不在你之前,所以t == u。

这是否符合我的意图?从不同的shared_ptr创建的两个weak_ptr总是以这种方式比较为不相等吗? 或者我错过了什么?

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

1 个答案:

答案 0 :(得分:25)

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

与标准一致的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实际上比较弱指针的相等性是合理的,因为您可以安全地比较控件阻止指针然后对象指针,因为如果两个弱指针具有相同的控制块,那么您知道两者都是或者两者都没有过期。也许是下一版标准的东西。