10

请考虑到我的经验不足,但我不明白std::owner_less.

我已经看到不建议使用mapwith as 键,因为过期的键会破坏地图,实际上:weak_ptrweak_ptr

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

这种行为有多不确定?我问的原因是因为文档owner_less

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

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

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

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

听起来它可能会变得比完全未定义更松弛。如果一个人的实现删除了过期的 weak_ptrs 并且根本没有或根本没有使用任何挥之不去的那些,那么行为何时变得未定义?

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

地图

我可以在文档中找到的唯一问题是上面引用的内容,过期的 weak_ptrs 将返回等效的。

根据这些docsweak_ptr ,对于不依赖于 ordering 也不使用 expired s的实现来说,这不是问题:

联想

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

已订购

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

地图

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

听起来如果一个实现不关心 order 也不使用 expired weak_ptrs 那么就没有问题,因为值是通过 key 而不是 order 引用的,因此findexpiredweak_ptr可能会返回另一个weak_ptrs 值,但因为它没有用在这个特定的实现中,除了是erased 之外,没有问题。

我可以看到需要使用weak_ptrordering 或 expired weak_ptrs 可能是一个问题,无论可能是什么应用程序,但所有行为似乎都远未定义,因此 a maporset似乎并没有被 expired 完全破坏weak_ptr

是否有更多的技术解释map,weak_ptrowner_less反驳这些文档和我的解释?

4

3 回答 3

4

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

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

要记住的一件事是,空的weak_ptr 是从未分配过有效shared_ptr 的,或者已分配了空的shared_ptr/weak_ptr 的。已过期的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 对象;否则,构造一个与 r 共享所有权的 weak_ptr 对象,并存储存储在 r 中的指针的副本。

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

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

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

于 2014-04-22T03:23:26.527 回答
3

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

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
}

mapset要求他们的密钥比较器对其密钥类型实施严格的弱排序关系。这意味着,除其他外,ifx小于ythenx总是小于。如果程序不保证这一点,则程序表现出未定义的行为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
}

如果 aweak_ptr过期,那么weak_ptr由于它为空,它随后将与其他比较不同,并且不能再保证严格的弱排序关系。在这种情况下,解决方法是相同的:使用不受共享状态更改影响的自定义比较器;owner_less就是这样一个比较器。


这种行为有多不确定?

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

如果一个人的实现 [...] 行为何时变得未定义?

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

如果一个人的实施 [...] 行为仍然未定义?换句话说,会不会find开始返回错误的键?

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

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

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

于 2014-04-22T02:42:03.663 回答
0

std::sort也需要订购。 owner_less对它可能有用。

在 amapset更少 - 将 aweak_ptr作为两者的关键是追求未定义的行为。因为无论如何您都将不得不手动同步容器和指针的生命周期,您也可以使用原始指针(或以某种方式处理过期问题的手动滚动的非拥有智能指针)以使其更清晰。

于 2014-04-22T02:08:57.363 回答