Стереть чужой итератор - PullRequest
       35

Стереть чужой итератор

2 голосов
/ 11 февраля 2012

Я экспериментировал со следующим кодом

list<int> a ={1,2};
list<int> b ={3,4};
a.erase(b.begin());
for (auto x:b) cout << x << endl;

Странно, программа работала нормально, без ошибок.То, что он печатает, это 4. Интересно, почему erase является функцией-членом, когда объект уже неявно присутствует в итераторе.

Ответы [ 5 ]

3 голосов
/ 11 февраля 2012
a.erase(b.begin());

Это вызывает неопределенное поведение, потому что вы передаете итератор, полученный из одного контейнера, в функцию другого контейнера.a и b - это два разных контейнера, они не одинаковы.

Неопределенное поведение означает, что все может произойти: оно может работать как положено, а может и нет.Ни спецификация языка, ни компилятор не дают гарантии, что он будет работать.Это точно сказано "неопределенное поведение".

Что вы должны сделать, это:

auto value = *(b.begin()); //value is int
auto it = std::find(a.begin(), a.end(), value); //returns iterator 
if ( it != a.end())
   a.erase(it); //well-defined, as the iterator belongs to the same container!

Или, если вы хотите удалить все элементы, равные value, тогда вы можете простосделайте это:

a.remove(value); //std::list has remove member function

Однако, если вы используете std::vector, который вы должны использовать в большинстве случаев.Это тип контейнера по умолчанию в C ++, и вы должны использовать std::list только в том случае, если у вас есть для этого веская причина:

std::vector<int> a ={1,2};
std::vector<int> b ={3,4};

//if you want to remove one element:
auto value = *(b.begin()); //value is int
auto it = std::find(a.begin(), a.end(), value); //returns iterator 
if ( it != a.end())
   a.erase(it); //well-defined, as the iterator belongs to the same container!

И если вы хотите удалить все элементы, равные value, тогдаВы можете применить популярную Erase-Remove Idiom как:

a.erase(std::remove(a.begin(), a.end(), value), a.end()); 

Обратите внимание, что std::vector не имеет remove() функции-члена, поэтому вы применяете эту идиому.Вы можете прочитать мой ответ здесь , который обсуждает это более подробно.

1 голос
/ 11 февраля 2012

Это просто неопределенное поведение , поэтому может случиться что угодно, и все, что вы наблюдаете , в некотором роде является "ожидаемым поведением".

Не делай этого.

Предварительным условием для std::list::erase является то, что аргумент должен быть итератором для элемента контейнера .

0 голосов
/ 12 февраля 2012

Это просто деталь реализации, если std::list::erase на самом деле не требует знания this. Это функция-член для согласованности с другими контейнерами. Как таковой, вы можете взять контейнер в качестве аргумента шаблона и вызвать container.erase(iter);

0 голосов
/ 11 февраля 2012

Другие отметили, что в соответствии со стандартом C ++ это приводит к неопределенному поведению.

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

template<class Tag>
inline void unlink(ListNode<Tag>* node)
{
    ListNode<Tag> *prev = node->prev_, *next = node->next_;
    prev->next_ = next;
    next->prev_ = prev;
    node->prev_ = node;
    node->next_ = node;
}

Ваш код работает правильно, потому что std::list<> часто реализуется в виде двойного связного списка, а list<>::erase() получает указатель на узел списка от итератора и выполняет код, аналогичный приведенному выше. Если вы включите поддержку отладки для итераторов, этот код, вероятно, приведет к утверждению во время выполнения.

0 голосов
/ 11 февраля 2012

C ++ - это стандарт, который не требует, чтобы итератор знал, к какому контейнеру он принадлежит.Мы не можем изменить Стандарт только потому, что в одной конкретной реализации функция может выполнять свою работу, не нуждаясь в конкретном параметре.

...