检查迭代器是不是有效
Posted
技术标签:
【中文标题】检查迭代器是不是有效【英文标题】:Checking if an iterator is valid检查迭代器是否有效 【发布时间】:2011-01-04 23:58:03 【问题描述】:有什么方法可以检查迭代器(无论它来自向量、列表、双端队列...)是否(仍然)可取消引用,即尚未失效?
我一直在使用try
-catch
,但是有没有更直接的方法呢?
示例:(不起作用)
list<int> l;
for (i = 1; i<10; i++)
l.push_back(i * 10);
itd = l.begin();
itd++;
if (something)
l.erase(itd);
/* now, in other place.. check if it points to somewhere meaningful */
if (itd != l.end())
// blablabla
【问题讨论】:
在 C++ 中,当您只是修改迭代器而不使用值时,您应该始终更喜欢++itd
而不是 itd++
。
在看到您的新代码示例后,请注意 STL 擦除方法返回下一个迭代器,它是一个有效的迭代器(尽管它可能是结束迭代器)。因此,为了帮助保持 itd 有效,您可以这样做: if (something) itd = l.erase(itd);
另请注意,R Samuel Klatchko 建议始终更喜欢前增量 (++itd) 而不是后增量 (itd++) 的原因是效率。由于 2 个运算符的实现方式不同,预增量总是更快。它不仅是与之相关的迭代器,还包括任何可以前后递增的值。
How to check whether STL iterator points at anything? 的可能重复项
注意:作为重复链接的问题已作为 this 问题的重复项(循环引用)关闭。
【参考方案1】:
我假设您的意思是“是一个有效的迭代器”,它没有因容器的更改而失效(例如,插入/擦除到向量/从向量中删除)。在这种情况下,不,您无法确定迭代器是否(安全地)可解引用。
【讨论】:
尽管如此,我认为是时候将Checked STL
引入竞争中了:经过检查的 stl 目标是捕获迭代器错误 > 使用无效迭代器或比较来自不同容器的迭代器。经过检查的 stl 的旅行绝对应该是您的测试套件的一部分;)
@Matthieu M :我认为这不会在不久的将来发生,因为这样做至少会花费 1. 保持指向每个引用向量的迭代器的指针 2. 当通过每个元素无效时性能鹰派将在数英里之外将其击落。 :(
@Ajeet:检查过的 STL 已经存在,通常在传统的 STL 中烘焙,但 #ifdef
ed out。它确实需要成本,减慢代码速度,但例如 MSVC 有 2 级检查,第一个非常容易访问(第二个肯定很慢......)请记住这显然仅适用于测试构建。
好吧,C++SL 准确地记录了每个容器成员函数,无论它是否使迭代器无效。就目前而言,您无法检查,但您可以知道。【参考方案2】:
正如 jdehaan 所说,如果迭代器没有失效并指向容器,您可以通过将其与 container.end()
进行比较来检查。
但是请注意,如果迭代器是单一的 - 因为它没有被初始化或者在容器上的变异操作后它变得无效(当你增加向量的容量时,向量的迭代器无效, 例如) -- 唯一允许您对其执行的操作是赋值。换句话说,您无法检查迭代器是否为单数。
std::vector<int>::iterator iter = vec.begin();
vec.resize(vec.capacity() + 1);
// iter is now singular, you may only perform assignment on it,
// there is no way in general to determine whether it is singular or not
【讨论】:
【参考方案3】:非便携式答案:是 - 在 Visual Studio 中
Visual Studio 的 STL 迭代器有一个“调试”模式可以做到这一点。您不希望在船舶构建中启用此功能(有开销),但在检查构建中很有用。
在 VC10 here 上了解它(该系统可以而且实际上确实会更改每个版本,因此请查找特定于您的版本的文档)。
编辑另外,我应该补充一点:Visual Studio 中的调试迭代器被设计为在您使用它们时立即爆炸(而不是未定义的行为);不允许“查询”他们的状态。
【讨论】:
就像这个答案的附录一样,LLVM 12.0 版提供了一个debug mode,可以提供类似的调试功能。它是通过使用_LIBCPP_DEBUG
宏启用的。旧版本的 LLVM(如 11)似乎也支持这一点。然而,这个宏的必要数字设置似乎取决于 LLVM 版本。【参考方案4】:
通常你通过检查它是否与 end() 不同来测试它,比如
if (it != container.end())
// then dereference
此外,使用异常处理来替换逻辑在设计和性能方面都不好。您的问题非常好,绝对值得在您的代码中进行替换。像名字所说的异常处理只能用于罕见的意外问题。
【讨论】:
那么,当你销毁迭代器在列表中指向的元素,或者一个位于向量之前的元素时,迭代器会指向末尾吗?就我而言,我没有...(我将编辑问题以使其更清楚) 删除和插入时,所有迭代器和引用可能会被破坏。因此,您最好在继续之前获得新的迭代器。这是因为一个例如。向量有时必须在添加新项目之前重新分配所有内存。这当然会使所有指针、引用和迭代器(在大多数情况下很像指针)都无效 @huff 您必须阅读 vector::erase 和 list::erase 的 API 文档才能理解其行为。此外,如果我没记错的话,对于 Microsoft 和 GCC 的 std::map::erase 实现,这里还有一些灰色区域(仍然是?)。 @huff 在这种情况下,所有迭代器都变得无效。有相当不错的书籍,例如 C++ Guru Scott Meyers 的 Effective STL 和更有效的 STL 或 Herb Sutter 的其他书籍,可以详细解释发生的事情。对于某些容器,擦除会返回一个迭代器,因此您可以安全地进一步迭代。container
是什么意思?是std::container
吗?或者你的意思是原始容器?如果我无法访问原始容器怎么办?【参考方案5】:
有什么方法可以检查迭代器(无论是来自向量、列表、双端队列...)是否(仍然)可解引用,即尚未失效?
不,没有。相反,您需要在迭代器存在时控制对容器的访问,例如:
当您的线程仍在使用该容器的实例化迭代器时,您的线程不应修改容器(使迭代器无效)
1234563线程在使用迭代器时修改容器)诸如捕获异常之类的变通方法将不起作用。
这是更普遍问题的一个具体实例,“我可以测试/检测指针是否有效吗?”,答案通常是“不,你不能测试它:相反,你必须管理所有内存分配和删除,以便知道任何给定的指针是否仍然有效”。
【讨论】:
在多线程场景中,这会很糟糕,不是吗?:l.erase(itd); itd = l.end(); - 另一个线程将 itd 与 l.end() 进行比较。 - 是的,我知道这并不完美,但是在擦除之后和分配之前其他线程介入的机会是如此遥远......呵呵:D 如果您编写自己的容器(而不是使用 STL),那么您可能 -- 1) 让容器跟踪(记住)当前构造的迭代器实例 2) 让容器的析构函数设置一个标志在每个迭代器的实例中 3)让迭代器的方法检查该标志(以在尝试访问之前验证容器是否仍然存在) 4)可选地以线程安全的方式执行此操作 5)对其他容器修改也执行类似的操作这可能会使迭代器无效(例如删除或添加容器中的元素)。 当我在上面说“不”时,我的意思是使用标准容器实现(设计得特别快,但不是特别安全)。【参考方案6】:尝试和捕捉是不安全的,如果你的迭代器“越界”,你不会,或者至少很少抛出。
正如 alemjerus 所说,迭代器总是可以被取消引用。不管下面有什么丑陋。很有可能迭代到内存的其他区域并写入可能保留其他对象的其他区域。我一直在看代码,无缘无故地观察变量的变化。这是一个很难检测到的错误。
记住,插入和删除元素可能会使所有引用、指针和迭代器失效。
我最好的建议是控制迭代器,并始终保持手头的“结束”迭代器能够测试您是否处于“行尾”。
【讨论】:
“可以取消引用”您可能的意思是:没有人会阻止您这样做。但是,取消引用无效的迭代器时会发生未定义的行为。【参考方案7】:在某些 STL 容器中,当您擦除迭代器的当前值时,当前迭代器将变为无效。发生这种情况是因为擦除操作改变了容器的内部内存结构,并且现有迭代器上的增量运算符指向未定义的位置。
当您执行以下操作时,迭代器会在传递给擦除函数之前先进行。
if (something) l.erase(itd++);
【讨论】:
【参考方案8】:有什么方法可以检查迭代器是否可解引用
是的,gcc debugging containers 可作为 GNU 扩展使用。对于std::list
,您可以改用__gnu_debug::list
。一旦尝试使用无效的迭代器,以下代码将中止。由于调试容器会带来额外的开销,因此它们仅适用于调试。
#include <debug/list>
int main()
__gnu_debug::list<int> l;
for (int i = 1; i < 10; i++)
l.push_back(i * 10);
auto itd = l.begin();
itd++;
l.erase(itd);
/* now, in other place.. check if itd points to somewhere meaningful */
if (itd != l.end())
// blablabla
$ ./a.out
/usr/include/c++/7/debug/safe_iterator.h:552:
Error: attempt to compare a singular iterator to a past-the-end iterator.
Objects involved in the operation:
iterator "lhs" @ 0x0x7ffda4c57fc0
type = __gnu_debug::_Safe_iterator<std::_List_iterator<int>, std::__debug::list<int, std::allocator<int> > > (mutable iterator);
state = singular;
references sequence with type 'std::__debug::list<int, std::allocator<int> >' @ 0x0x7ffda4c57ff0
iterator "rhs" @ 0x0x7ffda4c580c0
type = __gnu_debug::_Safe_iterator<std::_List_iterator<int>, std::__debug::list<int, std::allocator<int> > > (mutable iterator);
state = past-the-end;
references sequence with type 'std::__debug::list<int, std::allocator<int> >' @ 0x0x7ffda4c57ff0
Aborted (core dumped)
【讨论】:
【参考方案9】:任何 std 容器的擦除函数的参数类型(正如您在问题中列出的那样,即它是否来自向量、列表、双端队列......)是 always 此容器的迭代器仅限。
这个函数使用第一个给定的迭代器从容器中排除这个迭代器指向的元素,甚至是后面的元素。一些容器只擦除一个迭代器的一个元素,而另一些容器则擦除一个迭代器之后的所有元素(包括该迭代器指向的元素)直到容器的末尾。如果擦除函数接收到两个迭代器,那么每个迭代器指向的两个元素都会从容器中删除,它们之间的所有其他元素也会从容器中删除,但重点是每个传递给任何 std 容器的擦除函数的迭代器变得无效! 还有:
每个指向某个已从容器中删除的元素的迭代器都变得无效,但它不会通过容器的末尾!
这意味着指向某个已从容器中删除的元素的迭代器无法与 container.end() 进行比较。 这个迭代器是无效的,所以它是不可解引用的,即不能使用 * 和 -> 操作符,它也不能递增,即不能使用 ++ 操作符,它也不能递减,即不能使用-- 运算符。
也没有可比性!!! IE。你甚至不能使用 == 和 != 运算符
实际上,您不能使用在 std 迭代器中声明和定义的任何运算符。 你不能用这个迭代器做任何事情,比如空指针。
使用无效的迭代器执行某些操作会立即停止程序,甚至导致程序崩溃并出现断言对话框窗口。无论您选择什么选项,单击什么按钮,都无法继续编程。您只需单击“中止”按钮即可终止程序和进程。
您不会对无效的迭代器执行任何其他操作,除非您可以将其设置为容器的开头,或者直接忽略它。
但是在决定如何处理迭代器之前,首先你必须知道这个迭代器是否无效,如果你调用了你正在使用的容器的擦除函数。
我自己制作了一个函数,可以检查、测试、知道并返回给定迭代器是否无效。您可以使用 memcpy 函数来获取任何对象、项目、结构、类等的状态,当然我们总是首先使用 memset 函数来清除或清空新缓冲区、结构、类或任何对象或项目:
bool IsNull(list<int>::iterator& i) //In your example, you have used list<int>, but if your container is not list, then you have to change this parameter to the type of the container you are using, if it is either a vector or deque, and also the type of the element inside the container if necessary.
byte buffer[sizeof(i)];
memset(buffer, 0, sizeof(i));
memcpy(buffer, &i, sizeof(i));
return *buffer == 0; //I found that the size of any iterator is 12 bytes long. I also found that if the first byte of the iterator that I copy to the buffer is zero, then the iterator is invalid. Otherwise it is valid. I like to call invalid iterators also as "null iterators".
我在发布之前已经测试过这个功能,发现这个功能对我有用。
非常希望我已经全面回答了你的问题,也对你帮助很大!
【讨论】:
对不起,这只是一组毫无意义的轶事,加上荒谬或有害的想法。 (A)erase
不会在其输入迭代器中删除“两个元素”; it#2 是过去式/排他性的。 (B) 这就是无效迭代器对您的实现一次所做的事情;我的可能永远不会崩溃,可能会在退出时崩溃,可能会从 GTK+ 中抛出一个完全随机的assert
,etc.... (B) 不要传播这种极其不安全的想法:所有迭代器都有大小相同,全部为 0x00 不知何故 是无效的标志(在memcpy
ing 之前有一点memset
ing 缓冲区;为什么? )...没有【参考方案10】:
有办法,但是丑。。。可以用std::distance
函数
#include <algorithms>
using namespace std
auto distance_to_iter = distance(container.begin(), your_iter);
auto distance_to_end = distance(container.begin(),container.end());
bool is_your_iter_still_valid = distance_to_iter != distance_to_end;
【讨论】:
【参考方案11】:使用带有增量的擦除:
如果(某事)l.erase(itd++);这样你就可以测试迭代器的有效性了。
【讨论】:
【参考方案12】:if (iterator != container.end())
iterator is dereferencable !
如果您的迭代器不等于 container.end()
,并且不可取消引用,那么您做错了。
【讨论】:
以上是关于检查迭代器是不是有效的主要内容,如果未能解决你的问题,请参考以下文章