我想比较两个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来访问集合中的对象。
答案 0 :(得分:25)
完全重写这个答案,因为我完全误解了。这是一个很难做到的事情!
与标准一致的std::weak_ptr
和std::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_equals
和owner_hash
将允许在无序容器中使用weak_ptr
,并且给定owner_equals
实际上比较弱指针的相等性是合理的,因为您可以安全地比较控件阻止指针然后对象指针,因为如果两个弱指针具有相同的控制块,那么您知道两者都是或者两者都没有过期。也许是下一版标准的东西。