3

I have a situation where I'm marching through a vector, doing things:

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();
    }
  }
}

Under normal conditions - assuming everything goes well - I march all the way to my_list.end() and end the loop successfully.

However, if something goes wrong while I'm doing stuff, I want to be able to undo everything - basically retrace my steps back to the very beginning of the vector, undoing everything one at a time in reverse order.

My problem is that when I get to my_list.begin() - as shown in the nested for loop - I'm really not done yet because I still need to call undoStuff() on my first element in the list. Now, I could just make the final call outside of the loop, but this seems a little unclean.

The way I see it, I'm only done when I get to my_list.rend(). However, I can't compare a std::vector::iterator to a std::vector::reverse_iterator.

Given what I'm trying to do, what's the best choice of iterator-type / loop combination?

Camelid
  • 1,535
  • 8
  • 21
Runcible
  • 7,006
  • 12
  • 42
  • 62

9 Answers9

8

I'm a little rusty when it comes to STL vectors, but would it be possible to create a std::vector::reverse_iterator from your initial iterator? Then you would only need to start at the last item you were at when going forward, and would be able to compare it to my_list.rend() to make sure that the first item is processed.

Andy
  • 30,088
  • 6
  • 78
  • 89
  • yes, you can do that. see here: http://www.gamedev.net/community/forums/topic.asp?topic_id=388555 – Matt J Mar 11 '09 at 00:26
4

There is of course no reason not to use the vectors operator[]() if that makes your code clearer, simpler and/or more efficient.

  • Not more efficient in most cases. Iterators are abstracted pointers for STL data types. [] works horribly for (STL) linked lists, for example. – strager Mar 11 '09 at 00:29
  • And I wasn't aware that std::list had an operator[] –  Mar 11 '09 at 00:37
  • Ah, sorry, Neil. Did not know that. To note, iterating through maps and sets is better with an iterator as well. Nice to keep consistency. – strager Mar 11 '09 at 00:39
  • @strager, you can't iterate through a map or set with operator[] either. Not sure what you are trying to say here. – Brian Neal Mar 11 '09 at 01:25
  • @Brian Neal, I'm saying that iteration using iterators is consistent across several STL containers, whereas using [] isn't. – strager Mar 11 '09 at 01:53
4

While using reverse iterators via rbegin() and rend() works nicely, unfortunately I find that converting between reverse and non-reverse iterarotrs tends to be quite confusing. I can never remember without having to go through a logic-puzzle exercise whether I need to increment or decrement before or after the conversion. As a result I generally avoid the conversion.

Here's the way I'd probably code your error handling loop. Note that I'd think that you wouldn't have to call undoStuff() for the iterator that failed - after all, doStuff() said it didn't succeed.

// 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();
}
Michael Burr
  • 333,147
  • 50
  • 533
  • 760
  • 1
    A simple way of thinking about iterators is that they are cursors at positions between elements. A forwards iterator will produce the element after the cursor when dereferenced, a reverse iterator will produce the element before the cursor when dereferenced. Equivalent forwards and reverse iterators are cursors that are at the same position. – Mankarse Sep 27 '11 at 04:03
2

Without using a reverse_iterator, you can walk backwards this way:

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

Though this creates a copy of iter, the cost shouldn't be too great. You can refactor for better speed:

while(iter != m_list.begin())
{
    --iter;
    iter->undoStuff();
}
Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
strager
  • 88,763
  • 26
  • 134
  • 176
  • If there is a failure on the first element, the while loop never gets entered? – Runcible Mar 11 '09 at 01:06
  • @Runcible, Ah, this is true. I did not notice this. Sorry. I will try updating my answer to fix this issue. – strager Mar 11 '09 at 01:13
  • @Runcible: should you be calling undoStuff() on the iteration that failed the doStuff() call? Of course, this depends on the behavior of the methods, but often you wouldn't (ie., you don't call fclose() for a failed fopen()). – Michael Burr Mar 11 '09 at 03:34
  • Oh my! You're quite correct. In that case, strager's original answer probably works just fine. – Runcible Mar 11 '09 at 05:04
  • While it can be fixed, it only works for random-access iterators. The far easier solution is to change to a do..while() loop. That way, you test for iter==begin() after decrementing. – MSalters Mar 11 '09 at 09:01
2

It depends on what your doStuff() function does, and how important performance is in your context. If possible, it would probably be clearer (ie - easier for the reader) to work on a copy of your vector, and only if everything is okay, swap the vectors.

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);
Gilad Naor
  • 20,752
  • 14
  • 46
  • 53
  • I would just use `std::vector`'s copy constructor and say `std::vector workingCopy = myVector;`. Stylistically, I would prefer to either have doStuff throw (assuming it's some sort of complex operation, I prefer exceptions whenever there is some sort of deep chain of calls that could fail at any point in the middle) or say `for (auto iter = workingCopy.begin(); iter != workingCopy.end(); ++iter) { if (!iter->doStuff()) return false; } return true;` And have that be its own function that takes workingCopy by reference. Use the return value of that to determine whether to swap. – David Stone Apr 17 '12 at 16:47
1

You need to use rbegin() to get a reversible iterator.

Personally I still prefer

for (int i=0;i<vecter.size();i++) { }
Martin Beckett
  • 94,801
  • 28
  • 188
  • 263
1

Ok, I'll go out on a limb here..

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())
Arnold Spence
  • 21,942
  • 7
  • 74
  • 67
  • Maybe I'm misreading this - but if there is an error on the first element, it seems like the iter-- in the second loop will go out of range and do Something Bad? – Runcible Mar 11 '09 at 01:00
  • I think you are right, at the very least, decrementing a begin() iterator may be undefined. Too bad, it will definitely cross the ugly line by having to replace iter-- with if(iter != my_list.begin()) iter--; :) – Arnold Spence Mar 11 '09 at 01:18
0

This is what I call over engineering, but it is so much fun

// 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()));
}
Ismael
  • 2,995
  • 29
  • 45
0

This can be done with a 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();
  }
}
Mankarse
  • 39,818
  • 11
  • 97
  • 141