如何有效地检查两个 std::weak_ptr 指针是否指向同一个对象?

问题描述 投票:0回答:1

我想比较两个 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 模板不提供相等运算符,据我了解,这是 有充分理由

所以一个幼稚的实现看起来像这样:

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来访问集合中的对象。

c++ c++11 shared-ptr smart-pointers weak-ptr
1个回答
35
投票

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

符合标准的

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
,比较弱指针是否相等实际上变得明智,因为您可以安全地比较控制块指针 然后 对象指针,因为如果两个弱指针具有相同的控制块,那么您就知道两个都已过期或都没有过期。也许是下一版本标准的内容。

最新问题
© www.soinside.com 2019 - 2024. All rights reserved.