繁体   English   中英

在std :: vector :: begin()之前减少std :: vector :: iterator

[英]Decrementing std::vector::iterator before std::vector::begin()

在向量之一的“迭代时删除”模式之后,我不明白为什么这个代码有效,或者它是否正在使用未定义的行为:

守则

#include <vector>
#include <iostream>

int main(int argc, char* argv[], char* envz[])
{
  std::vector<std::string> myVec;
  myVec.push_back("1");
  myVec.push_back("2");
  myVec.push_back("3");

  for (std::vector<std::string>::iterator i = myVec.begin();
       i != myVec.end();
       ++i)
  {
    if ("1" == *i)
    {
      std::cout << "Erasing " << *i << std::endl;
      i = myVec.erase(i);
      --i;
      continue;
    }
    std::cout << *i << std::endl;
  }

  return 0;
}

输出

>g++ -g main.cpp
>./a.out 
Erasing 1
2
3

问题

考虑for循环的第一次迭代:

  • i myVec.begin(),“指向” 1
  • 我们输入条件块。
  • 删除1并将i设置为擦除元素之后的一个,即2 ,myVec.begin()现在也指向该元素
  • 我减少了i ,所以现在它指向... myVec.begin()之前的一个???

我很困惑为什么这似乎有效,正如输出所证明的那样,但是在减少迭代器方面感觉很可疑。 如果条件为if ("2" == *i) ,则此代码很容易合理化,因为迭代器减量仍将其置于向量中的有效条目。 即如果我们有条件地擦除2i将被设置为指向3 ,但随后手动递减并因此指向1 ,然后是for循环增量,将其设置为再次指向3 有条不紊地擦除最后一个元素同样容易遵循。

我还在尝试什么

这个观察使我假设在vector :: begin()之前递减是幂等的,所以我尝试了额外的减量,如下:

#include <vector>
#include <iostream>

int main(int argc, char* argv[], char* envz[])
{
  std::vector<std::string> myVec;
  myVec.push_back("1");
  myVec.push_back("2");
  myVec.push_back("3");

  for (std::vector<std::string>::iterator i = myVec.begin();
       i != myVec.end();
       ++i)
  {
    if ("1" == *i)
    {
      std::cout << "Erasing " << *i << std::endl;
      i = myVec.erase(i);
      --i;
      --i;      /*** I thought this would be idempotent ***/
      continue;
    }
    std::cout << *i << std::endl;
  }

  return 0;
}

但这导致了一个段错误:

Erasing 1
Segmentation fault (core dumped)

有人可以解释为什么第一个代码块工作,特别是为什么擦除第一个元素后的单个减量有效?

不,您的代码具有未定义的行为:如果i == myVec.begin() ,则i = myVec.erase(i); 导致i再次成为( myVec.begin()的新值myVec.begin() ,并且--i具有未定义的行为,因为它超出了迭代器的有效范围。

如果你不想使用erase-remove习惯用法(即myVec.erase(std::remove(myVec.begin(), myVec.end(), "1"), myVec.end()) ),那么手动循环while-mutating看起来像这样:

for (auto it = myVec.begin(); it != myVec.end(); /* no increment! */) {
  if (*it == "1") {
    it = myVec.erase(it);
  } else {
    ++it;
  }
}

无论如何,这里和原始代码中的关键点是erase 使迭代器无效 ,因此在擦除之后必须使用有效值重新赋值迭代器。 我们实现这一点归功于erase的返回值,这正是我们需要的新的有效迭代器。

这可能在某些编译器中起作用,但在其他编译器中可能会失败(例如,编译器可能实际上在运行时检查您在begin()下没有递减并在这种情况下抛出异常 - 我相信至少有一个编译器会这样做但不会记住哪一个)。

在这种情况下,一般模式是不在for但在循环内增加:

  for (std::vector<std::string>::iterator i = myVec.begin();
       i != myVec.end();
       /* no increment here */)
  {
    if ("1" == *i)
    {
      std::cout << "Erasing " << *i << std::endl;
      i = myVec.erase(i);
      continue;
    }
    std::cout << *i << std::endl;
    ++i;
  }

使用向量时,错误的迭代实际上可能在更多情况下起作用,但如果您尝试使用std::mapstd::set ,则会遇到非常糟糕的时间。

这里的关键是在递减之后continue 通过调用它, ++i将在解除引用i之前由循环迭代触发。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM