суббота, 12 июля 2008 г.

Processing STL containers

Уже не первый раз в ходе code review натыкаюсь на ошибку связанную с организацией обработки того или иного контейнера. Суть проблемы в организации цикла обработки, как показано в примере:
typedef std::list<sometype_t> list_t;
list_t sample_list;
void f( void ) {
// Remove items from list
for ( list_t::iterator it = sample_list.begin(); it != sample_list.end(); ++it ) {
if ( it->condition == true ) {
it = sample_list.erase( it ); // <- errors here !!!
}
}
}


Для начинающих разработчиков, по всей видимости, этот код кажется безобидным. Однако, если внимательно посмотреть, то можно заметить, что после удаления элемента происходит заход на следующий цикл. А что там? Там первым делом происходит ++it, т. е. мы перескакиваем через элемент следующий за удаленным. Плохо? Конечно, но и это ещё не всё. Если мы удалили последний элемент в списке, то it окажется за пределами sample_list.end() и цикл, по всей видимости, завершится уже только из-за ошибки access violation.

Чтобы избежать таких недоразумений лучше написать что-то вроде:
void f( void ) {
// Remove items from list
list_t::iterator it = sample_list.begin();
while ( it != sample_list.end() ) {
if ( it->condition == true ) {
it = sample_list.erase( it );
} else {
++it;
}
}
}


Ещё один интересный момент, это использование такой конструкции для удаления:
sample_list.erase( it++ );

Смотрится симпатичнее варианта со знаком равенства. Однако тут тоже кроется опасность. Если контейнером будет std::list, то ошибки не произойдет. Но что, если мы решили поменять тип контейнера на std::vector, например? А будет вот что: it++ запомнит текущее значение it, которое затем и будет передано в erase – тут все неплохо. Однако перед вызовом erase итератор it будет указывать на следующий элемент контейнера из-за переаллокации элементов контейнера. Для std::vector после работы erase в этом месте будет элемент, следующий за тем, что был нужен. А в худшем случае – опять access violation.

Комментировать в ВКонтакте