【问题标题】:vector::erase and reverse_iteratorvector::erase 和 reverse_iterator
【发布时间】:2012-07-14 11:17:22
【问题描述】:

我在 std::vector 中有一组元素,这些元素从第一个元素开始按降序排序。我必须使用向量,因为我需要将元素放在连续的内存块中。而且我有一个集合,其中包含许多具有所描述特征的向量实例(始终按降序排序)。

现在,有时,当我发现较大集合(包含这些向量的集合)中有太多元素时,我会以类似于以下伪代码的方式丢弃这些向量中的最小元素:

grand_collection: collection that holds these vectors
T: type argument of my vector
C: the type that is a member of T, that participates in the < comparison (this is what sorts data before they hit any of the vectors).

std::map<C, std::pair<T::const_reverse_iterator, std::vector<T>&>> what_to_delete;
iterate(it = grand_collection.begin() -> grand_collection.end())
{
     iterate(vect_rit = it->rbegin() -> it->rend())
     {
         // ...
          what_to_delete <- (vect_rit->C, pair(vect_rit, *it))
          if (what_to_delete.size() > threshold)
               what_to_delete.erase(what_to_delete.begin());
         // ...  
     }
}

现在,在运行此代码后,在what_to_delete 中,我有一组迭代器指向我想从这些向量中删除的原始向量(整体最小值)。请记住,原始向量在遇到此代码之前已排序,这意味着对于任何what_to_delete[0 - n],位置n - m 上的迭代器都不会指向比n 更远离同一向量开头的元素,其中m &gt; 0。

从原始向量中擦除元素时,我必须将 reverse_iterator 转换为迭代器。为此,我依赖 C++11 的 §24.4.1/1:

reverse_iterator和iterator的关系是 &*(reverse_iterator(i)) == &*(i-1)

这意味着要删除vect_rit,我使用:

vector.erase(--vect_rit.base());

现在,根据 C++11 标准§23.3.6.5/3:

迭代器擦除(常量迭代器位置);效果:无效 在擦除点或之后的迭代器和引用。

这如何与 reverse_iterators 一起工作? reverse_iterators 内部是否通过引用向量的真实开始 (vector[0]) 来实现,并将该 vect_rit 转换为经典迭代器,以便擦除是安全的?还是 reverse_iterator 使用 rbegin()(即vector[vector.size()])作为参考点,删除任何远离向量 0-index 的内容仍然会使我的反向迭代器无效?

编辑:

看起来 reverse_iterator 使用 rbegin() 作为其参考点。以我描述的方式擦除元素在删除第一个元素后给了我关于不可引用迭代器的错误。而在插入到what_to_delete 时存储经典迭代器(转换为const_iterator)时工作正常。

现在,为了将来参考,标准是否指定了在随机访问 reverse_iterator 的情况下应将什么视为参考点?或者这是一个实现细节?

谢谢!

【问题讨论】:

  • 问题是关于标准的字母,还是关于常见的实现?
  • 据我了解,您没有/不想在这里使用reverse_iterator。 std::vector 具有随机访问迭代器,这意味着您可以使用从 .end() 开始的常规 iterator 并将其向后移动。这样一来,您就不必做太多的魔法来使用.erase()。
  • 我不相信reverse_iterator 甚至没有您所说的“参考点”。它只是一个普通的双向/随机访问迭代器的便利包装器;特别是,reverse_iterator 的失效语义是“当且仅当包装的 Iterator 有效时,reverse_iterator 才有效。”在我看来,主要的混淆点是由reverse_iterator 包裹的实际Iterator 指向的位置比reverse_iterator 似乎指向的位置晚。
  • 我只是好奇为什么你有那么多关于向量集合和std::map&lt;C, std::pair&lt;T::const_reverse_iterator, std::vector&lt;T&gt;&amp;&gt;&gt; 的词,而似乎所有问题的真正意义在于向量中的reverse_iterator 是否会失效当它用于擦除元素时。还是我根本不理解真正的问题?

标签: c++ stl vector iterator


【解决方案1】:

从标准的角度来看(我承认,我不是该标准的专家):来自 §24.5.1.1:

namespace std {
    template <class Iterator>
    class reverse_iterator ...
    {
        ...
            Iterator base() const; // explicit
        ...
        protected:
            Iterator current;
        ...
    };
}

从 §24.5.1.3.3 开始:

Iterator base() const; // explicit
    Returns: current.

因此,在我看来,只要您不删除 vector 中的任何内容,在您的 reverse_iterators 指向之前,reverse_iterator 就应该保持有效。

当然,根据您的描述,有一个问题:如果您的向量中有两个连续的元素最终要删除,那么您 vector.erase(--vector_rit.base()) 的事实意味着您已经使 reverse_iterator 无效"指向” 指向前一个元素,因此您的下一个 vector.erase(...) 是未定义的行为。

以防万一这很清楚,让我换种说法:

std::vector<T> v=...;
...
// it_1 and it_2 are contiguous
std::vector<T>::reverse_iterator it_1=v.rend();
std::vector<T>::reverse_iterator it_2=it_1;
--it_2;

// Erase everything after it_1's pointee:

// convert from reverse_iterator to iterator
std::vector<T>::iterator tmp_it=it_1.base();

// but that points one too far in, so decrement;
--tmp_it;

// of course, now tmp_it points at it_2's base:
assert(tmp_it == it_2.base());

// perform erasure
v.erase(tmp_it);  // invalidates all iterators pointing at or past *tmp_it
                  // (like, say it_2.base()...)

// now delete it_2's pointee:
std::vector<T>::iterator tmp_it_2=it_2.base(); // note, invalid iterator!

// undefined behavior:
--tmp_it_2;
v.erase(tmp_it_2);

在实践中,我怀疑您会遇到两种可能的实现:更常见的是,底层的iterator 只不过是一个(适当包装的)原始指针,因此一切都可以完美运行。不太常见的是,迭代器实际上可能会尝试跟踪失效/执行边界检查(Dinkumware STL 没有在调试模式下编译时做过这样的事情吗?),并且可能会对你大喊大叫。

【讨论】:

    【解决方案2】:

    在问题中,您已经准确引用了标准所说的 reverse_iterator 是:

    reverse_iterator和iterator的关系是&*(reverse_iterator(i)) == &*(i- 1)

    请记住,reverse_iterator 只是底层迭代器 (reverse_iterator::current) 之上的“适配器”。正如您所说,reverse_iterator 的“参考点”是包装的迭代器current。 reverse_iterator 上的所有操作实际上都发生在该底层迭代器上。您可以使用reverse_iterator::base() 函数获取该迭代器。

    如果您删除--vect_rit.base(),您实际上是在删除--current,因此current 将失效。

    附带说明,表达式--vect_rit.base() 可能并不总是编译。如果迭代器实际上只是一个原始指针(vector 可能就是这种情况),那么vect_rit.base() 返回一个右值(C++11 术语中的右值),因此预减运算符不起作用因为该运算符需要一个可修改的左值。请参阅 Scott Meyers 的“Effective STL”中的“第 28 项:了解如何使用reverse_iterator 的基础iterator”。 (该项目的早期版本可以在http://www.drdobbs.com/three-guidelines-for-effective-iterator/184401406的“指南3”中在线找到)。

    您可以使用更难看的表达式(++vect_rit).base() 来避免这个问题。或者因为您正在处理向量和随机访问迭代器:vect_rit.base() - 1

    无论哪种方式,vect_rit 都被擦除无效,因为vect_rit.current 无效。

    但是,请记住,vector::erase() 返回一个有效的迭代器,指向刚刚删除的元素的新位置。您可以使用它来“重新同步”vect_rit:

    vect_rit = vector_type::reverse_iterator( vector.erase(vect_rit.base() - 1));
    

    【讨论】:

      【解决方案3】:

      reverse_iterator 和普通的iterator 一样,指向向量中的某个位置。实现细节无关紧要,但如果您必须知道,它们(在典型实现中)都只是内部的普通旧指针。不同的是方向。反向迭代器将其 + 和 - 反转 w.r.t。常规迭代器(还有++ 和--、&gt; 和&lt; 等)。

      这很有趣,但并不意味着主要问题的答案。

      如果您仔细阅读该语言,它会说:

      在擦除点或之后使迭代器和引用无效。

      引用没有内置的方向感。因此,语言显然是指容器自身的方向感。擦除点之后的位置是具有较高索引的位置。因此,迭代器的方向在这里无关紧要。

      【讨论】:

        猜你喜欢
        • 2013-02-18
        • 2015-04-01
        • 1970-01-01
        • 1970-01-01
        • 2011-02-10
        • 2015-03-24
        • 2017-04-16
        • 1970-01-01
        相关资源
        最近更新 更多