2013-08-10 2 views
2

У меня есть этот кусок кода:Вектор получает итерация раза больше, чем размер()

for (std::vector<Marker>::iterator it = markers.begin(); it != markers.end(); ++it) { 
    if (it->getDots().size() < 3) { 
     markers.erase(it); 
    } 
} 

В одном из тестовых входов (приложение делает анализ изображения) я получаю Segfault. Я попытался отладить код (безрезультатно) и заметил одну вещь. При запросе gdb на p markers.size() я получаю $9 = 3. Поэтому я ожидал бы, что цикл будет повторяться три раза, но на удивление он делает это (по крайней мере) 5 раз. На пятой итерации есть segfault. Я также заметил, что это не разыменование *it (здесь it->), что вызывает ошибку. Это конкретно it->getDots(), который является простым геттером.

Я пишу на C++ очень редко, поэтому это может быть какая-то простая ошибка, но ни моя отладка, ни поисковая система не принесли никакого решения. Не могли бы вы помочь?

Я хотел бы подчеркнуть, что на разных входах (несколько разных изображений) эта функция работает правильно, поэтому мне еще сложнее проследить ошибку.

+1

Вы являетесь жертвой [аннулирования итератора] (http://stackoverflow.com/questions/3747691/stdvector-iterator-invalidation). – Mahesh

+0

Не могу поверить, что в Интернете есть так много примеров об итераторских операциях, таких как «iterator-> doSomething()», но почти никто не включает предупреждение об этой особой ситуации, которая может быть довольно распространенной, поскольку удаление вещей из коллекции представляется довольно простой операцией. Но также и плохо, я должен был внимательно прочитать документацию. Спасибо вам, ребята! – Wojtek

ответ

6

vector::erase аннулирует все итераторы, указывающие на элемент, стертые, и все элементы, которые следуют. Таким образом, it становится недействительным, а выражение ++it на следующей итерации цикла демонстрирует неопределенное поведение.

Лучший способ закодировать эту логику - erase-remove idiom.

2

Вам необходимо обновить it когда вы стираете:

it = markers.erase(it); 

так erase будет «перемен» vector, и ваш текущий it больше не действует (и только делать it++ если, когда вы не стереть).

Однако более распространенным способом сделать это, чтобы использовать этот тип конструкции:

markers.erase(std::remove(markers.begin(), markers.end(), number_in), markers.end()); 
+0

Если у него нет C++ (и он может использовать лямбда), для этого требуется определить отдельную функцию или функциональный объект для его условия (и использовать 'remove-if'). Это может быть или не быть оправдано, в зависимости от остальной части его заявления. (По моему опыту, если вам нужен один раз один раз, вам обычно нужно что-то подобное в другом месте, и обычно стоит попытаться определить функциональный объект.) Конечно, если у него есть C++ 11, лямбда решает проблему элегантно , –

3

Проблема эта линия:

markers.erase(it); 

Итератор недействительным. Но это нормально, erase возвращает действительный итератор:

auto it = markers.begin(); 
while(it != markers.end()) 
{ 
    if(it->getDots().size() < 3) { 
     it = markers.erase(it); 
    } 
    else ++it; 
} 
+0

Именно так, потому что мне нравится держать вещи многословными, особенно на C++ (в которых я не эксперт). Это может быть немного более трудоемким (для процессора), но мои векторы составляют не более 9 элементов, поэтому не слишком большое влияние на производительность. – Wojtek

Смежные вопросы