vector::erase and reverse_iterator
vector::erase and reverse_iterator
我有一个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
的"参考点"是那个封装的迭代器current
。reverse_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
一样,指向矢量中的某个位置。实现细节是无关紧要的,但如果您必须知道的话,它们(在一个典型的实现中)只是内部的普通旧指针。不同之处在于方向。反向迭代器的+
和-
与常规迭代器(以及++
和--
、>
和<
等)相反。
这很有趣,但并不意味着主要问题的答案。
如果你仔细阅读语言,它会说:
在擦除点处或之后使迭代器和引用无效。
引用没有内置的方向感。因此,该语言显然是指容器自身的方向感。擦除点之后的位置是那些具有较高索引的位置。因此,迭代器的方向在这里无关紧要。
- 错误:未在此范围内声明'reverse'
- 在调试模式下引发C++ "deque iterator not dereferencable"异常
- std::iterator::reference 必须是引用吗?
- 为什么unordered_set<string::iterator>不起作用?
- 造成致命错误:boost/fusion/iterator/equal_to.hpp 没有这样的文件或目录
- Visual accept std::string from std::byte iterator
- 使用to_string、reverse、stoi组合的C++反转编号给出运行时错误实例超出范围
- 什么是"std::set<int,int>::iterator"?
- 从 std::vector 中删除项目时"Iterator not incrementable"
- 如何从 boost::container::vector<std::string>::iterator 访问索引和对象?
- 空集"Out of bound iterator"
- 如何使用set<pair<int,int> >::iterator itrator it迭代set<pair<int,int> >st中的值?
- "How to make a recursive call for palindrom numbers without reverse function in c++?"
- 给定一个类型为 Container:<T>:Iterator 的函数参数,如何为某些类型的 T 实现特定的重载?
- 带升压async_read_some "String iterator not dereferncable"
- C++为什么"deque iterator not dereferencable "
- 错误:与'operator='不匹配(操作数类型为"std::map<int、double>::iterator
- 自动,错误:MAP ITERATOR没有名为“ First”的成员
- set<shared_ptr<T>>::iterator to set<shared_ptr<t const>>::const_iterator
- 标准::复制失败,"cannot seek vector iterator after end"