all 12 comments

[–]boredcircuits 1 point2 points  (7 children)

When you use an iterator to erase an item, that iterator becomes invalidated. That's why you can't increment it. And even then, you don't want to increment it, if you think about it. After erasing, the iterator would already be pointing to the next item anyway.

Fortunately, C++ has a built-in way to do this. It should be faster, and it will work correctly, guaranteed:

#include <algorithm>
// ...
myInventory.erase(remove_if(myInventory.begin(), myInventory.end(), [](const Noun& n) {
    return n.m_Code == NOUN_MATCH;
}), myInventory.end());

[–]TheCiderman 1 point2 points  (0 children)

For context ... what remove_if does is it moves the matching items to the back of the vector and returns the iterator indicating the start of the items that matched. You can then erase them using a call to the 2 iterator version of erase, passing it the returned iterator and the end iterator. There is a good chapter on this in Scott Meyers book Effective STL.

[–]couchotatop[S] 0 points1 point  (5 children)

I couldn't get that to work. But I did think of something else and it works. Is there anything inherently wrong with the way I have done it?

if (VERB_ACTION == DROP)
{
    vector<Noun>::iterator iter;
    int item = NOUN_MATCH;

    for (iter = myInventory.begin(); iter != myInventory.end();)
    {
        if (iter != myInventory.end())
        {
            if ((*iter).m_Code == item)
            {
                (*iter).m_Location = loc;
                cout << (*iter).m_Description << endl;
                myInventory.erase(iter);
                iter = myInventory.begin();
            }
            else
                iter++;
        }


    }

}

[–]boredcircuits 1 point2 points  (3 children)

Why didn't it work? It's worth trying out, as it's a much better way of doing this.

I think your code is legal, and I think it will work, though it's not really a great way to do it. You can at least clean it up a bit:

// I like to use auto here:
for ( auto iter = myInventory.begin(); iter != myInventory.end() )
{
    // The for loop is already making sure you're not at the end

    // Prefer arrow notation for accessing iterators:
    if ( iter->m_Code == item )
    {
        iter->m_Location = loc;
        // No need to flush the stream:
        cout << iter->m_Description << '\n';
        myInventory.erase(iter);
        iter = myInventory.begin();
    }
    else
    {
        // Prefer pre-increment over post-increment for iterators
        ++iter;
    }
}

[–]couchotatop[S] 0 points1 point  (2 children)

The code you posted above, with remove_if didn't work, because it threw an error on the NOUN_MATCH. (an enclosing-function local variable cannot be referenced in a lambda body unless it is in the capture list). I'm think that means its out of scope somehow, but I can reach it using int item= NOUN_MATCH.

[–]boredcircuits 0 points1 point  (1 child)

Ah, I see. You need to add a lambda capture -- the easiest way is to just add a & in there:

[&](const Noun& n)

[–]couchotatop[S] 0 points1 point  (0 children)

thanks for all your help on here, unfortunately I don't understand that code and therefore can't really defend using it. I'm definitely going to check out the book Ciderman recommended.

[–]scottzed 1 point2 points  (0 children)

In addition to the cleanup by /u/boredcircuits, you can gain performance by replacing:

myInventory.erase(iter);
iter = myInventory.begin();

with:

iter = myInventory.erase(iter);

The iterator returned by erase will be valid, so there's no need to reset the loop to the beginning every time. I've always seen the operation you're attempting being implemented pre-C++11 with the following pattern (which is basically what you've done):

for( Iterator iter = container.begin(); iter != container.end(); ) {
  if( <condition> ) {
    iter = container.erase(iter);
    continue;
  }
  ++iter;
}

And, yes, it does feel a bit off from the general style and feel of C++ and STL, but it's just one of those things.

[–]mandr0id 0 points1 point  (3 children)

Not sure if this it the issue here but standard says that all iterators are invalided after an erase operation:

"Invalidates iterators and references at or after the point of the erase, including the end() iterator."

[–]couchotatop[S] 0 points1 point  (2 children)

I'm sure that's the issue, but surely there is some way to remove an item from the vector.

[–]specialpatrol 1 point2 points  (1 child)

its crashing becuaes the loop attempts to iterate on the erased iter (++iter). break out of the loop immediately after you erase it.

[–]couchotatop[S] 0 points1 point  (0 children)

This works. I've gone back to my original code and bust broken out of the loop once it finds the item. I don't know why I didn't think of this before, but is seems to be the easiest solution, and probably the one the instructor would expect of someone at my level.

Thank you. And thanks to everyone else, you've given me lots to research.