我在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 > 0
。
vect_rit
当从原始向量中删除元素时,我必须将reverse_iterator转换为迭代器。为此,我依赖于C ++ 11的§24.4.1/ 1:
reverse_iterator和迭代器之间的关系是 &amp; *(reverse_iterator(i))==&amp; *(i-1)
这意味着删除vector.erase(--vect_rit.base());
,我使用:
§23.3.6.5/3
现在,根据C ++ 11标准vector[0]
:
迭代器擦除(const_iterator位置);效果:无效 擦除点处或之后的迭代器和引用。
这如何与reverse_iterators一起使用? reverse_iterators是在内部实现的,是对向量的真实开头(vector[vector.size()]
)的引用,并将该vect_rit转换为经典迭代器,那么擦除是否安全?或者reverse_iterator使用rbegin()(const_iterator
)作为参考点并删除任何远离vector 0-index的内容仍会使我的反向迭代器无效?
修改
看起来reverse_iterator使用rbegin()作为参考点。以我描述的方式擦除元素在删除第一个元素后给出了关于非可引用迭代器的错误。而在插入what_to_delete
时存储经典迭代器(转换为{{1}})的工作正常。
现在,为了将来参考,标准是否指定在随机访问reverse_iterator的情况下应该将哪些视为参考点?或者这是一个实现细节?
谢谢!
答案 0 :(得分:3)
从标准的角度来看(我承认,我不是标准专家):来自§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_iterator
之前没有删除reverse_iterator
中的任何内容,就说vector.erase(--vector_rit.base())
应保持有效。< / p>
当然,根据你的描述,有一个问题:如果你的向量中有两个连续的元素,你最终想要删除,那么你reverse_iterator
意味着你已经使{{{}无效1}}“指向”前一个元素,因此您的下一个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在调试模式下一次编译时没有这样做吗?),并且可能会对你大喊大叫。
答案 1 :(得分:2)
在问题中,您已经准确引用了reverse_iterator
标准所说的内容:
reverse_iterator和迭代器之间的关系是&amp; *(reverse_iterator(i))==&amp; *(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()
将返回一个rvalue(以C ++ 11术语表示的prvalue),因此预减量运算符因为该运算符需要可修改的左值,所以不会对它起作用。请参阅Scott Meyers的“有效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));
答案 2 :(得分:0)
reverse_iterator
与正常iterator
一样,指向向量中的某个位置。实现细节是无关紧要的,但是如果你必须知道,它们(在一个典型的实现中)都只是里面的普通旧指针。不同的是方向。反向迭代器的+
和-
反转了w.r.t.常规迭代器(以及++
和--
,>
和<
等)。
这很有趣,但并不能真正暗示对主要问题的回答。
如果您仔细阅读该语言,则说:
在擦除点或之后使迭代器和引用无效。
参考文献没有内置的方向感。因此,语言清楚地指的是容器自身的方向感。擦除点之后的位置是具有较高索引的位置。因此,迭代器的方向与此无关。