【问题标题】:insert elements while iterating a sorted stl container在迭代排序的 stl 容器时插入元素
【发布时间】:2019-09-23 00:57:09
【问题描述】:

当我的代码在迭代时将元素插入 std::set 时,我发现了意外结果。我需要对此有所启发。

这里是测试代码:

    template<class Ti, class T>
    void iteration_insertion(Ti start, Ti end, T& S){
        for (auto ite=start;ite!=end;++ite){
            auto before=*ite;
            if(*ite % 2)
                S.insert(*ite*2);
            else
                S.insert(*ite/2);
            if(before!=*ite)
                cout<<before<<","<<*ite<<endl;
        }
    }
    void test() {
        set<int> S1({4,7,10,13}),S2(S1);
        cout<<"ascending\n";
        iteration_insertion(S1.begin(),S1.end(),S1);
        cout<<"descending\n";
        iteration_insertion(S2.rbegin(),S2.rend(),S2);
    }

结果:

ascending
descending
13,26

我们可以看到迭代器指向的元素在插入后有时会发生变化。但我不知道什么时候会发生。在测试代​​码中它只发生了一次,降序为 13。为什么在升序迭代中没有这种不匹配?为什么在降序迭代中 7 没有不匹配?如何防止它发生?我很好,新的附加值可以在以后迭代,这是意料之中的。我只是不希望迭代器被插入改变。

测试代码可以是一种通用的启发式实践:从每个当前状态生成新状态以供进一步检查。

【问题讨论】:

  • 感谢@chris 的回答,我想最简单的解决方案不是使用反向迭代器,而是使用 std::greater 作为比较器的前向

标签: c++ stl iterator


【解决方案1】:

为了考虑容器的所有元素,反向迭代器会存储元素的迭代器,超过您在取消引用时获得的迭代器。例如,rbegin() 的结果在内部存储了一个过去的迭代器。当您取消引用时,会生成存储的迭代器的副本,并且该副本在取消引用之前会递减。

本质上是使用的标准代码的简化版本:

template<typename Iter>
struct reverse_iterator {
    Iter base;

    auto& operator++() { --base; return *this; }
    auto& operator*() {
        auto copy = base;
        --copy;
        return *copy;
    }
};

auto std::set::rbegin() {
    return reverse_iterator{this->end()};
}

将此应用于您的情况,您从rbegin() 开始。取消引用它会给你最后一个元素 13。当你插入 26 时,它会在 13 之后插入,但在内部存储在 ite 中的最后一个迭代器之前。当您再次取消引用 ite 时,会生成一个内部过去的迭代器的副本,并将其递减到 13 和 26 之间的位置,然后取消引用以给出 26。

这是解释的图片版本,以防有帮助:

【讨论】:

  • 对于前向迭代器(以begin()开头),或者对于反向迭代器但不指向rbegin(),插入后会不会出现这样的不匹配?
  • @jerron,不适用于前向迭代器,因为您可以将迭代器视为附加到它“指向”的拆分之后的元素。对于std::set,迭代器将有某种指针指向包含该元素的树中的节点,并且插入不会改变已经存在的节点中的内容。对于反向迭代器,只要在未反向集中的当前元素之后立即插入一个元素,就会发生这种情况,因为内部迭代器连接到“当前”迭代器之后的节点,而不是直接连接到这个节点。
  • @jerron,你可以see an example 我的意思。基迭代器没有改变,但是当前元素随着插入而改变,因为在基迭代器之前有一个新元素。还值得指出的是,从标准的角度来看,前向情况被迭代器失效保证所涵盖。
【解决方案2】:

一个std::set

包含一个排序集

的价值。强调“排序”部分。向集合does not invalidate any existing iterator 中插入一个新值,因此插入是有效的,就其本身的优点而言。

但是,由于容器 总是 包含一个“排序”集,如果新插入的值大于您的迭代器引用的值,并且您推进迭代器向前,在某些时候您将迭代新值,因为它现在是集合的一部分,必须始终按排序顺序。

如果新值小于迭代器当前引用的值,根据定义,向前推进迭代器不会迭代新值。

没有办法“防止它发生”,因为根据定义,std::sets 就是这样工作的。您将需要自己“防止它发生”。也许通过将任何新值插入到单独的 std::vector 中,并在完成迭代后,将来自该向量的累积新值插入到您的集合中。

【讨论】:

  • 但是为什么在代码的“升序”部分没有这样的行为呢?使用“4”,我们插入一个“2”,就像我们在后面的代码中使用“13”一样。另外,为什么“7”在降序中没有不匹配?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-06
  • 2016-10-11
  • 2013-04-16
  • 2012-01-05
相关资源
最近更新 更多