72

I have a vector that holds items that are either active or inactive. I want the size of this vector to stay small for performance issues, so I want items that have been marked inactive to be erased from the vector. I tried doing this while iterating but I am getting the error "vector iterators incompatible".

vector<Orb>::iterator i = orbsList.begin();

    while(i != orbsList.end()) {
        bool isActive = (*i).active;

        if(!isActive) {
            orbsList.erase(i++);
        }
        else {
            // do something with *i
            ++i;
        }
    }
Lucas
  • 1,577
  • 6
  • 18
  • 25
  • See this related question: http://stackoverflow.com/questions/347441/erasing-elements-from-a-vector – Naveen Jan 17 '11 at 12:30

8 Answers8

76

The most readable way I've done this in the past is to use std::vector::erase combined with std::remove_if. In the example below, I use this combination to remove any number less than 10 from a vector.

(For non-c++0x, you can just replace the lambda below with your own predicate:)

// a list of ints
int myInts[] = {1, 7, 8, 4, 5, 10, 15, 22, 50. 29};
std::vector v(myInts, myInts + sizeof(myInts) / sizeof(int));

// get rid of anything < 10
v.erase(std::remove_if(v.begin(), v.end(), 
                       [](int i) { return i < 10; }), v.end());
Robert F.
  • 455
  • 3
  • 14
Moo-Juice
  • 38,257
  • 10
  • 78
  • 128
  • 1
    While elegant in the simple case, I'm dubious of its effectiveness in the real world. This wouldn't even work at all with a dynamic size vector. – Josh Sanders Jul 25 '15 at 05:14
  • 12
    "This wouldn't even work at all with a dynamic size vector." Why not? – jhasse Jul 18 '17 at 11:51
  • While the `std::remove_if` lambda looks nice, but if I only want to delete exactly one element that matches the condition, it still goes through each element to the end. Therefore, I prefer manual iteration so I can break from the loop any time. – daparic May 09 '20 at 11:16
67

I agree with wilx's answer. Here is an implementation:

// curFiles is: vector < string > curFiles;

vector< string >::iterator it = curFiles.begin();

while(it != curFiles.end()) {

    if(aConditionIsMet) {

        it = curFiles.erase(it);
    }
    else ++it;
}
DanSkeel
  • 3,853
  • 35
  • 54
Vassilis
  • 2,878
  • 1
  • 28
  • 43
19

You can do that but you will have to reshuffle your while() a bit, I think. The erase() function returns an iterator to the element next after the erased one: iterator erase(iterator position);. Quoting from the standard from 23.1.1/7:

The iterator returned from a.erase(q) points to the element immediately following q prior to the element being erased. If no such element exists, a.end() is returned.

Though maybe you should be using the Erase-remove idiom instead.

wilx
  • 17,697
  • 6
  • 59
  • 114
  • Also, to downsize the vector(as it seems to what the OP wants to do) the swap trick can be used. Details here:http://stackoverflow.com/questions/253157/how-to-downsize-stdvector – Naveen Jan 17 '11 at 12:50
  • 1
    It might be enough to say `i=orbsList.erase(i)` instead of `orbsList.erase(i++)` – jakebman Jan 17 '11 at 23:16
9

erase returns a pointer to the next iterator value (same as Vassilis):

vector <cMyClass>::iterator mit
for(mit = myVec.begin(); mit != myVec.end(); )
{   if(condition)
        mit = myVec.erase(mit);
    else
        mit++;
}
Pierre
  • 4,114
  • 2
  • 34
  • 39
4

If someone need working on indexes

vector<int> vector;
for(int i=0;i<10;++i)vector.push_back(i);

int size = vector.size();
for (int i = 0; i < size; ++i)
{
    assert(i > -1 && i < (int)vector.size());
    if(vector[i] % 3 == 0)
    {
        printf("Removing %d, %d\n",vector[i],i);
        vector.erase(vector.begin() + i);
    }

    if (size != (int)vector.size())
    {
        --i;
        size = vector.size();
        printf("Go back %d\n",size);
    }
}
Gelldur
  • 11,187
  • 7
  • 57
  • 68
1

As they said, vector's iterators get invalidated on vector::erase() no matter which form of iterator increment you use. Use an integer index instead.

Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
  • 2
    Using the return value of erase you get a valid iterator back. So it's only necessary to assign the returned value to the iterator -- no performance issue here. – harper Jan 17 '11 at 12:46
  • @harper: Actually, there's a big performance issue here; erasing an item from the middle of a vector requires all the rest of them to be moved down, which involves O(N) constructor and destructor calls each time. – Oliver Charlesworth Jan 17 '11 at 13:00
  • And the use of an index has what befinit? How is the erasing performance increased? I just annotated the statement "iterators get invalidated .. no matter which form"... – harper Jan 17 '11 at 15:02
1

You might want to consider using a std::list instead of a std::vector for your data structure. It is safer (less bug prone) to use when combining erasure with iteration.

Raedwald
  • 46,613
  • 43
  • 151
  • 237
0

Removing items from the middle of a vector will invalidate all iterators to that vector, so you cannot do this (update: without resorting to Wilx's suggestion).

Also, if you're worried about performance, erasing items from the middle of a vector is a bad idea anyway. Perhaps you want to use an std::list?

Oliver Charlesworth
  • 267,707
  • 33
  • 569
  • 680
  • 4
    I know its a little late but you might wanna read this. Its contrary to what most people would expect but it comes from the creator of C++ http://bulldozer00.com/2012/02/09/vectors-and-lists/ – EFreak Feb 16 '13 at 22:19