views:

316

answers:

8

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

std::vector::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?

+8  A: 

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
yes, you can do that. see here: http://www.gamedev.net/community/forums/topic.asp?topic_id=388555
Matt J
+3  A: 

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

anon
Not more efficient in most cases. Iterators are abstracted pointers for STL data types. [] works horribly for (STL) linked lists, for example.
strager
Well, I did say "if" :-)
anon
And I wasn't aware that std::list had an operator[]
anon
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
@strager, you can't iterate through a map or set with operator[] either. Not sure what you are trying to say here.
Brian Neal
@Brian Neal, I'm saying that iteration using iterators is consistent across several STL containers, whereas using [] isn't.
strager
+1  A: 

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

Personally I still prefer

for (int i=0;i<vecter.size();i++) { }
Martin Beckett
+2  A: 

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

while(iter + 1 != m_list.begin())
{
    iter->undoStuff();
    --iter;
}
strager
If there is a failure on the first element, the while loop never gets entered?
Runcible
@Runcible, Ah, this is true. I did not notice this. Sorry. I will try updating my answer to fix this issue.
strager
@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
Oh my! You're quite correct. In that case, strager's original answer probably works just fine.
Runcible
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
+1  A: 

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
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
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
+2  A: 

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
A: 

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
+1  A: 

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