【问题标题】:Arithmetic on invalidated iterators无效迭代器的算术运算
【发布时间】:2020-10-05 20:01:53
【问题描述】:

在一对 std::vector 迭代器上调用 std::distance 的行为是否未定义,这些迭代器已因移动 vector 而无效?

对于上下文: 我正在为具有vector 数据和vector 指向该数据的迭代器的类编写复制和移动构造函数。一旦我将数据移动到它的目的地,我需要转换迭代器的向量以指向新的容器。我想避免在内存中创建中间索引表示。

【问题讨论】:

  • 根据标准 - 不,因为一个迭代器不再可以从另一个迭代器访问。在实践中它可能会正常工作,但我不会以这种方式使用它。
  • @RinatVeliakhmedov “按照标准” 可以链接段落吗?
  • @ThomasSablik 我假设这里的 cppreference 部分来自标准 en.cppreference.com/w/cpp/iterator/distance“如果 InputIt 不是 LegacyRandomAccessIterator,如果 last 不能通过(可能重复)首先递增从第一个到达,则行为未定义。如果InputIt 是 LegacyRandomAccessIterator,如果 last 不能从 first 到达并且 first 不能从 last 到达,则行为未定义。"
  • 所以这是XY problem。我会退后一步,重新关注最初的问题,而不是针对该问题提出的解决方案引起的问题。
  • @TedLyngmo 移动矢量。

标签: c++ vector iterator invalidation


【解决方案1】:

在一对 std::vector 迭代器上调用 std::distance 的行为是否未定义,这些迭代器已因移动 vector 而无效?

如果迭代器在移动之前有效,那么它们在移动之后仍然有效 - 因此您无需使用 std::distance 重新计算它们。

下面强调我的

std::vector::vector

在容器移动构造之后,指向other 的引用、指针、和迭代器(除了结束迭代器)仍然有效,但引用现在位于*this 中的元素。

当前标准通过[container.requirements.general/12] 中的一揽子声明做出此保证,并且正在考虑通过LWG 2321 提供更直接的保证。

[container.requirements.general/12] 声明

除非另有说明(明确地或通过根据其他函数定义函数),调用容器成员函数或将容器作为参数传递给库函数不应使迭代器无效到,或更改该容器内对象的值。

移动赋值运算符也有相同的总括语句,这意味着根据标准,迭代器在移动后保持有效。

LWG 2321 中的当前措辞暗示了如果图书馆工作组最终确定标准中的新段落会是什么样子——这似乎很难。 LWG 2321 于 2013 年开业。

容器(array 除外)的任何移动构造函数(或当allocator_traits<allocator_type>::propagate_on_container_move_assignment::valuetrue 时移动赋值运算符)会使引用源容器元素的任何引用、指针或迭代器无效。 [注意: end() 迭代器不引用任何元素,因此它可能会失效。 — 尾注]

如果太模糊,你可以使用

[container.requirements.general/11.6]

没有swap() 函数使引用被交换容器的元素的任何引用、指针、或迭代器无效。 [ 注意:end() 迭代器不引用任何元素,因此它可能会失效。 ——尾注]

如果迭代器在swap 之前 有效,那么它们在swap 之后 有效。

这是一个使用swap 保证的示例类:

#include <vector>

class Foo {
    std::vector<int> data{};
    std::vector<decltype(data)::iterator> dits{};

public:
    Foo() = default;

    Foo(const Foo&) = delete;    // here, dits would need to be calculated

    // A move constructor guaranteed to preserve iterator validity.
    Foo(Foo&& rhs) noexcept {
        data.swap(rhs.data);
        dits.swap(rhs.dits);
    }

    Foo& operator=(const Foo&) = delete;

    // A move assignment operator guaranteed to preserve iterator validity.
    Foo& operator=(Foo&& rhs) noexcept {
        data.swap(rhs.data);
        dits.swap(rhs.dits);
        return *this;
    }

    ~Foo() = default;
};

【讨论】:

  • 问题是关于计算两个无效迭代器之间的距离。那么无效状态就没有问题了。
  • @NicolasDusart 是的,但是当我读到这个问题时,我将其解释为 OP 认为迭代器只是 因为 的移动而失效 - 所以我想明确表示只要迭代器有效,你可以交换目标容器,迭代器仍然有效。
  • @NicolasDusart ... 现在 OP 已经验证了我的怀疑,我已经阅读了更多关于它的内容,并声称即使是移动也可以保留迭代器的有效性。
  • @NicolasDusart 不,我不是这个意思 :-) 我以为你很好奇,所以我标记了你。
猜你喜欢
  • 2014-11-13
  • 2012-07-02
  • 1970-01-01
  • 1970-01-01
  • 2013-08-17
  • 1970-01-01
  • 2015-10-24
  • 2013-11-25
  • 2018-08-17
相关资源
最近更新 更多