vector::erase and reverse_iterator

vector::erase and reverse_iterator

本文关键字:iterator reverse and erase vector      更新时间:2023-10-16

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

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

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 > 0)离同一向量开头更远的元素。

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

reverse_iterator和迭代器之间的关系是&(reverse_iterator(i))==&(i-1)

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

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

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

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

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

编辑:

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

现在,为了将来参考,该标准是否规定了在随机访问反向器的情况下应将什么作为参考点?或者这是一个实现细节?

谢谢!

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

反推器与迭代器之间的关系是&(reverse_iterator(i))==&(i-1)

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

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

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

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

无论哪种方式,由于vect_rit.current无效,所以vect_rit通过擦除而无效。

但是,请记住,vector::erase()将一个有效的迭代器返回到刚刚擦除的元素后面的元素的新位置。您可以使用它来"重新同步"vect_rit:

vect_rit = vector_type::reverse_iterator( vector.erase(vect_rit.base() - 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.

因此,在我看来,只要你在其中一个reverse_iterator指向之前没有擦除vector中的任何内容,所说的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在调试模式下编译时不是做过这样的事情吗?),并且可能会对你大喊大叫。

reverse_iterator与普通iterator一样,指向矢量中的某个位置。实现细节是无关紧要的,但如果您必须知道的话,它们(在一个典型的实现中)只是内部的普通旧指针。不同之处在于方向。反向迭代器的+-与常规迭代器(以及++--><等)相反。

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

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

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

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