Equality-compare std :: weak_ptr

fat*_*yte 28 c++ shared-ptr weak-ptr c++11

我想比较两个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));
Run Code Online (Sandbox Code Playgroud)

weak_ptr模板不提供相等的运算符,正如我所理解的那样,这是有充分理由的.

所以一个天真的实现看起来像这样:

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;
}
Run Code Online (Sandbox Code Playgroud)

如果第一个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);
}
Run Code Online (Sandbox Code Playgroud)

检查u的所有者块是否在"t"之前并且t不在u之前,所以t == u.

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

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

eca*_*mur 27

完全重写这个答案,因为我完全误解了.要做对,这是一个棘手的事情!

通常的实现的std::weak_ptr并且std::shared_ptr是与标准相一致是有两个堆中的对象:被管理对象,和一个控制块.引用同一对象的每个共享指针包含指向对象和控制块的指针,同样包含每个弱指针.控制块记录共享指针的数量和弱指针的数量,并在共享指针的数量达到0时解除分配管理对象; 当弱指针的数量也达到0时,控制块本身被释放.

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

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

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

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

所以你的equals代码绝对正确且线程安全.

问题是它不一致,shared_ptr::operator==因为它比较了对象指针,两个具有相同控制块的共享指针可以指向不同的对象(如上所述).

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

但是,比较控制块的开销较小(因为它不需要查看控制块),并且会给出与==不使用别名指针相同的结果.


我认为这里的标准有些不足之处; 添加一个owner_equalsowner_hash允许weak_ptr在无序容器中使用,并且鉴于owner_equals它实际上比较弱指针的相等性是合理的,因为你可以安全地比较控制块指针然后比较对象指针,因为如果两个弱指针具有相同的控制块,那么你知道无论是两者还是两者都没有过期.也许是下一版标准的东西.

  • 顺便说一句,[微软的家伙](http://channel9.msdn.com/Events/GoingNative/GoingNative-2012/STL11-Magic-Secrets)找到了一种方法来省略控制块中的指针与他的"我们 - 通过在控制块内分配实际对象来"知道你在哪里" - "优化".另请查看[幻灯片](http://view.officeapps.live.com/op/view.aspx?src=http%3a%2f%2fecn.channel9.msdn.com%2fevents%2fGoingNative12%2fGN12STL11.pptx)在他的4.幻灯片上,有一个"通常"实现的简洁图表. (2认同)
  • @Ben @ecatmur:实际上有一个添加 `owner_hash` 和 `owner_equal` 的提案([P1901R2](https://isocpp.org/files/papers/P1901R2.html)),已被批准包含在内在 C++26 中,请参阅:https://github.com/cplusplus/papers/issues/649。它已经集成到当前的标准草案中,请参阅:[mem.syn](https://eel.is/c++draft/memory.syn)。 (2认同)