什么是使用迭代器遍历和反遍历std::vector的最简洁方法?

3

我遇到了一个情况,我正在遍历一个向量并进行一些操作:

std::vector<T>::iterator iter = my_list.begin();

for ( ; iter != my_list.end(); ++iter )
{
  if ( iter->doStuff() )   // returns true if successful, false o/w
  {
    // Keep going...
  }
  else
  {
    for ( ; iter != m_list.begin(); --iter )  // ...This won't work...
    {
      iter->undoStuff();
    }
  }
}

在正常情况下——假设一切顺利——我会一直向 my_list.end() 前进并成功结束循环。
但是,如果在执行操作时出现问题,我希望能够撤消所有操作——基本上是按照相反的顺序逐个返回到向量的最开始,逆序撤销每一步操作。
我的问题是,当我到达 my_list.begin()(如嵌套循环中所示)时,我还没有完成,因为我仍然需要调用列表中第一个元素上的 undoStuff()。现在,我可以在循环外面进行最后一次调用,但这似乎有点不干净。
在我看来,只有当我到达 my_list.rend() 时才算完成。然而,我不能将 std::vector::iteratorstd::vector::reverse_iterator 进行比较。
鉴于我所要做的事情,哪种迭代器类型/循环组合是最好的选择?
9个回答

8

对于STL向量,我有点生疏,但是从你的初始迭代器创建std::vector::reverse_iterator是否可行?这样,当向前移动时,只需要从最后一个位置开始,并能将其与my_list.rend()进行比较以确保第一个项已被处理。


是的,你可以这样做。请参考这里:http://www.gamedev.net/community/forums/topic.asp?topic_id=388555 - Matt J

4
使用rbegin()rend()反向迭代器的效果不错,但是转换正向和反向迭代器之间的操作往往会令人困惑。我无法记住在转换前后需要增加还是减少,除非我进行一次逻辑谜题练习。因此,我通常避免转换。

这是我可能会编写您的错误处理循环的方式。请注意,在迭代器失败后,我认为您不必调用undoStuff() - 毕竟,doStuff()已经表示它没有成功。

// handle the situation where `doStuff() failed...

// presumably you don't need to `undoStuff()` for the iterator that failed
// if you do, I'd just add it right here before the loop:
//
//     iter->undoStuff();

while (iter != m_list.begin()) {
    --iter;
    iter->undoStuff();
}

1
迭代器的简单思考方式是它们是在元素之间位置的光标。前向迭代器在解引用时将产生光标后面的元素,而反向迭代器在解引用时将产生光标前面的元素。等效的前向和反向迭代器是处于相同位置的光标。 - Mankarse

4

当然,如果使用向量operator[]()能使您的代码更清晰、更简单和/或更高效,那么没有理由不使用。


在大多数情况下并不更高效。迭代器是STL数据类型的抽象指针。例如,[] 在(STL)链表中表现得很糟糕。 - strager
我不知道std::list有一个operator[]。 - anon
啊,抱歉,尼尔。我不知道那个。需要注意的是,通过迭代器遍历映射和集合也更好。保持一致性很好。 - strager
@strager,您也不能通过operator[]遍历map或set。不确定您想说什么。 - Brian Neal
@Brian Neal,我的意思是使用迭代器进行迭代在多个STL容器中是一致的,而使用[]则不是。 - strager

2

不使用reverse_iterator,您可以通过以下方式向后遍历:

while(iter-- != m_list.begin())
{
    iter->undoStuff();
}

尽管这会创建一个 iter 的副本,但成本不应太高。您可以进行重构以获得更快的速度:
while(iter != m_list.begin())
{
    --iter;
    iter->undoStuff();
}

如果第一个元素失败,while循环就不会被执行了吗? - Runcible
@Runcible,啊,这是真的。我没有注意到这一点。抱歉。我会尝试更新我的答案来解决这个问题。 - strager
@Runcible:在失败的doStuff()调用的迭代上调用undoStuff()是否合适?当然,这取决于方法的行为,但通常情况下不会这样做(例如,对于失败的fopen()不会调用fclose())。 - Michael Burr
哦,我的天!你说得很正确。在这种情况下,strager的原始答案可能完全有效。 - Runcible
虽然可以修复,但仅适用于随机访问迭代器。更简单的解决方案是改为使用do..while()循环。这样,在递减后测试iter==begin()。 - MSalters

2

这取决于您的doStuff()函数的功能以及在您的上下文中性能的重要性。如果可能的话,最好是(即-对于读者更容易)在副本上工作,并仅在一切正常时交换向量。

std::vector<Foo> workingCopy;
workingCopy.assign(myVector.begin(), myVector.end());

bool success = true;
auto iter = workingCopy.begin();
for( ; iter != workingCopy.end() && success == true; ++iter )
    success = iter->doStuff();

if( success )
    myVector.swap(workingCopy);

我会直接使用std::vector的复制构造函数,然后写成std::vector<Foo> workingCopy = myVector;。从风格上来说,我更喜欢让doStuff抛出异常(假设它是某种复杂操作,在任何中间点都可能失败时,我更喜欢使用异常),或者写成for (auto iter = workingCopy.begin(); iter != workingCopy.end(); ++iter) { if (!iter->doStuff()) return false; } return true;并将其作为一个以引用方式传递workingCopy的独立函数。使用该函数的返回值来确定是否进行交换。 - David Stone

1

你需要使用rbegin()来获取可逆迭代器。

个人而言,我仍然更喜欢

for (int i=0;i<vecter.size();i++) { }

1

好的,我会冒险尝试一下...

std::vector iterator iter = my_list.begin();
bool error = false;

while(iter != my_list.end())
{
  error = !iter->doStuff();
  if(error)
    break
  else
    iter++;
}

if(error)
do
{
  iter->undoStuff();
  iter--;
} 
while(iter != my_list.begin())

也许我理解错了,但如果第一个元素出现错误,似乎第二个循环中的iter--将超出范围并产生一些问题? - Runcible
我认为你是对的,至少递减begin()迭代器可能是未定义的。很遗憾,必须用if(iter != my_list.begin()) iter--;来替换iter--,这肯定会越过丑陋的界限 :) - Arnold Spence

0

这可以通过使用 reverse_iterator 来完成:

bool shouldUndo(false);
std::vector::iterator iter(my_list.begin()), end(my_list.end());
for ( ; iter != end && !shouldUndo; ++iter )
{
  shouldUndo = iter->doStuff();   // returns true if successful, false o/w
}
if (shouldUndo) {
  reverse_iterator<std::vector::iterator> riter(iter), rend(my_list.rend());
  //Does not call `undoStuff` on the object that failed to `doStuff`
  for ( ; riter != rend; ++riter )
  {
    iter->undoStuff();
  }
}

0

这就是我所说的过度工程,但它非常有趣

// This also can be done with adaptators I think
// Run DoStuff until it failed or the container is empty
template <typename Iterator>
Iterator DoMuchStuff(Iterator begin, Iterator end) {
  Iterator it = begin;
  for(; it != end; ++it) {
    if(!*it->DoStuff()) {
      return it;
    }
  }
  return it;
}

// This can be replaced by adaptators
template <typename Iterator>
void UndoMuchStuff(Iterator begin, Iterator end) {
  for(Iterator it = begin; it != end; ++it) {
    it->UndoStuff();
  }
}

// Now it is so much easier to read what we really want to do
typedef std::vector<MyObject*> MyList;
typedef MyList::iterator Iterator;
typedef MyList::reverse_iterator ReverseIterator;
Iterator it = DoMuchStuff(my_list.begin(), my_list.end());
if(it != my_list.end()) {
  // we need to unprocess [begin,it], ie including it
  UndoMuchStuff(ReverseIterator(1+it), ReverseIterator(my_list.begin()));
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接