如果一个过期的weak_ptr会给出未定义的行为,那么owner_less有什么意义呢

What is the point of owner_less if an expired weak_ptr will give undefined behavior?

本文关键字:owner 那么 less 什么 未定义 一个 过期 如果 ptr weak      更新时间:2023-10-16

请考虑到我的经验不足,但我不理解std::owner_less的要点。

我已经看到,不建议使用以weak_ptr为密钥的map,因为过期的weak_ptr密钥会破坏映射,实际上:

如果它过期,那么容器的顺序就被破坏了,之后尝试使用容器将产生未定义的行为。

这种行为有多不明确?我问这个问题的原因是因为文档中提到了owner_less:

此函数对象提供基于所有者(而不是基于值)的std::weak_ptr和std::shared_ptr的混合类型排序。顺序是这样的,只有当两个智能指针都为空或都管理同一对象时,即使通过get()获得的原始指针的值不同(例如,因为它们指向同一对象内的不同子对象)

同样,这是我的经验不足,但听起来map不会被过期的weak_ptr:完全破坏

返回weak_ptr对象是空的,还是它所属的所有者组中没有更多的shared_ptr。

过期指针在锁定时充当空的weak_ptr对象,因此不能再用于恢复拥有的shared_ptr。

听起来它可能会变得更加松弛,而不是完全不确定。如果一个实现删除了过期的weak_ptr,并且根本没有或根本没有使用任何延迟的weak_Ptr,那么行为何时会变得未定义?

如果一个人的实现不考虑顺序,只需要一种方便的方式来将weak_ptr与数据关联,那么行为是否仍然未定义?换句话说,find是否会开始返回错误的密钥?

地图

我在文档中能找到的唯一问题是上面提到的,过期的weak_ptr将返回等效的。

根据这些文档,对于不依赖订购也不使用过期weak_ptrs:的实现来说,这不是问题

关联

关联容器中的元素由它们的键引用,而不是由它们在容器中的绝对位置引用。

已订购

容器中的元素始终遵循严格的顺序。所有插入的元素都按此顺序给定一个位置。

地图

每个元素都将一个键与映射值相关联:键用于标识其主要内容为映射值的元素。

这听起来像是,如果一个实现不关心顺序,也不使用过期的weak_ptrs,那么就没有问题,因为值是由键而不是按顺序引用的,所以finding一个过期的weak_ptr可能会返回另一个weak_ptrs值,但由于在这个特定的实现中除了erased之外没有其他用途,所以没有问题。

我可以看到,无论是什么应用程序,使用weak_ptr排序或过期的weak_ptr都可能是一个问题,但所有行为似乎都远未定义,因此mapset似乎不会被过期的CCD20完全破坏。

mapweak_ptrowner_less是否有更多的技术解释反驳了这些文献和我的解释?

澄清一点。使用owner_less时,过期的weak_ptr不是UB。来自标准

在运算符()定义的等价关系下!运算符()(a,b) &amp!operator()(b,a),两个shared_ptr或weak_ptr实例等价于当且仅当它们共享所有权或都为空时。

需要记住的一件事是,一个空的weak_ptr是从未被分配过有效的shared_ptr的,或者是被分配了一个空shared_ptr/weak_prt的。已过期的weak_ptr不是空的weak_ptr。

编辑:

上面的定义取决于"空"weak_ptr意味着什么。那么,让我们看看标准

  • constexpr weak_ptr()noexcept;

    效果:构造一个空的weak_ptr对象
    后置条件:use_count()==0。

  • weak_ptr(const weak_ptr&r)noexcept
  • 模板weak_ptr(const weak_ptr&r)noexcept
  • 模板weak_ptr(const shared_ptr&r)noexcept;

    要求:第二个和第三个构造函数不应参与过载解决,除非Y*可以隐式转换为T*。

    效果:如果r为空,则构造一个空weak_ptr对象;否则,构造一个weak_ptr对象,该对象共享与r的所有权,并存储存储在r中的指针的副本。

    后置条件:use_count()==r.use_count

交换只是交换内容,赋值定义为上面的构造函数加上交换。

要创建一个空的weak_ptr,您可以使用默认构造函数,或者向它传递一个为空的weak_ptr或shared_ptr。现在,您将注意到过期实际上并不会导致weak_ptr变空。它只是使其use_count()为零,expired()返回true。这是因为在共享对象的所有弱指针也被释放之前,底层引用计数不能被释放。

下面是一个演示相同问题的最小示例:

struct Character
{
    char ch;
};
bool globalCaseSensitive = true;
bool operator< (const Character& l, const Character& r)
{
    if (globalCaseSensitive)
        return l.ch < r.ch;
    else
        return std::tolower(l.ch) < std::tolower(r.ch);
}
int main()
{
    std::set<Character> set = { {'a'}, {'B'} };
    globalCaseSensitive = false; // change set ordering => undefined behaviour
}

CCD_ 27和CCD_ 28要求它们的密钥比较器在它们的密钥类型上实现严格的弱排序关系。这意味着,除其他外,如果x小于y,则x总是小于y。如果程序不能保证这一点,则程序会显示未定义的行为

我们可以通过提供一个忽略区分大小写开关的自定义比较器来修复这个例子:

struct Compare
{
    bool operator() (const Character& l, const Character& r)
    {
        return l.ch < r.ch;
    }
};
int main()
{
    std::set<Character, Compare> set = { {'a'}, {'B'} };
    globalCaseSensitive = false; // set ordering is unaffected => safe
}

如果一个weak_ptr过期,那么该weak_ptr随后将由于其为空而与其他CCD_34进行不同的比较,并且不能再保证严格的弱排序关系。在这种情况下,修复方法是一样的:使用一个自定义比较器,它不受共享状态变化的影响;CCD_ 35就是这样一个比较器。


这种行为有多不明确?

未定义是未定义的。没有连续体。

如果一个人的实现[…],行为何时会变得不明确?

只要包含的元素不再具有定义明确的严格弱序关系。

如果一个人的实现[…]行为仍然没有定义?换句话说,find会不会开始返回错误的密钥?

未定义的行为不仅限于返回错误的密钥。它可以做任何事情

这听起来像是[…]没有问题,因为值是按键引用的,而不是按顺序引用的。

如果没有排序,键就缺乏引用值的内在能力。

std::sort也需要订购。CCD_ 38在其上可能是有用的。

mapset中,不那么重要——将weak_ptr作为其中一个的关键就是追求未定义的行为。由于您无论如何都必须手动同步容器和指针的生存期,因此您还可以使用原始指针(或手动滚动的非拥有智能指针,以某种方式处理过期问题)来使其更清晰。

相关文章: