Как вызвать стирание с обратным итератором


181

Я пытаюсь сделать что-то вроде этого:

for ( std::list< Cursor::Enum >::reverse_iterator i = m_CursorStack.rbegin(); i != m_CursorStack.rend(); ++i )
{
    if ( *i == pCursor )
    {
        m_CursorStack.erase( i );
        break;
    }
}

Однако стирание требует итератор, а не обратный итератор. Есть ли способ преобразовать обратный итератор в обычный итератор или другой способ удалить этот элемент из списка?


17
Кроме того, при написании таких циклов не следует повторно вычислять конечный итератор, как здесь i != m_CursorStack.rend(). Вместо этого пиши i = m_CursorStack.rbegin(), end = m_CursorStack.rend(); i != end;. То есть инициализируйте итератор, который вы можете использовать для повторного сравнения - при условии, что конечная позиция не изменится как побочный эффект вашего тела цикла.
SEH

Мне кажется, что очевидный вопрос здесь заключается в том, почему вы вообще это делаете. Что вы получаете от обхода списка в обратном порядке? Что вы получаете, написав этот код самостоятельно вместо использования std::remove?
Джерри Коффин

И является ли итератор в std :: list все еще действительным для увеличения после того, как элемент, на который он ссылается, был удален?
Стив Джессоп

3
Я хочу удалить только 1 элемент, поэтому «break;», используя «remove», избавит от любого, что соответствует, занимает больше времени и не делает то, что я хочу. Элемент, который я хочу удалить в этом конкретном случае, почти всегда будет концом списка или очень близок к нему, поэтому итерация в обратном порядке также быстрее и лучше подходит для решения проблемы.
0xC0DEFACE

4
stackoverflow.com/a/2160581/12386 Это говорит о том, что разработчики специально не определяют реализацию, потому что вы, как пользователь, не должны знать или заботиться, хотя @seh выше ожидает, что мы просто волшебным образом узнаем, что rend () вычисляется и дорого.
Stu

Ответы:


181

После еще нескольких исследований и испытаний я нашел решение. По-видимому, согласно стандарту [24.4.1 / 1] связь между i.base () и i:

&*(reverse_iterator(i)) == &*(i - 1)

(из статьи доктора Доббса ):

альтернативный текст

Поэтому вам нужно применить смещение при получении base (). Поэтому решение:

m_CursorStack.erase( --(i.base()) );

РЕДАКТИРОВАТЬ

Обновление для C ++ 11.

reverse_iterator iне изменяется:

m_CursorStack.erase( std::next(i).base() );

обратный_тератор iрасширен:

std::advance(i, 1);
m_CursorStack.erase( i.base() );

Я нахожу это намного яснее, чем мое предыдущее решение. Используйте то, что вам нужно.


27
Вы должны принять к сведению немного больше статьи, которую вы цитировали - чтобы быть переносимым, выражение должно быть m_CursorStack.erase( (++i).base())(чувак, от этой работы с обратными итераторами у меня болит голова ...). Следует также отметить, что статья DDJ включена в книгу Мейера «Effective STL».
Майкл Берр

8
Я считаю, что эта диаграмма более запутанная, чем полезная. Поскольку rbegin, ri и rend фактически указывают на элемент справа от того, на что они нарисованы, чтобы указывать. Диаграмма показывает, к какому элементу вы бы обратились, если бы вы *их использовали, но мы говорим о том, на какой элемент вы бы указывали, если бы вы baseих использовали, то есть один элемент справа. Я не такой фанат --(i.base())или (++i).base()решений, поскольку они мутируют итератор. Я предпочитаю (i+1).base()что работает, а также.
mgiuca

4
Обратные итераторы являются лжецами. Когда защищено, обратный итератор возвращает элемент перед ним . Смотрите здесь
bobobobo

4
Просто чтобы быть абсолютно ясным, этот метод все еще не может использоваться в нормальном цикле for (где итератор увеличивается обычным образом). См stackoverflow.com/questions/37005449/...
logidelic

1
m_CursorStack.erase ((++ i) .base ()) выглядит так, как будто это было бы проблемой, если бы ++ помог вам пройти последний элемент. Вы можете вызвать erase on end ()?
Stu

16

Обратите внимание, что это m_CursorStack.erase( (++i).base())может быть проблемой, если используется в forцикле (см. Оригинальный вопрос), потому что это меняет значение i. Правильное выражениеm_CursorStack.erase((i+1).base())


4
Вам нужно было бы создать копию итератора и сделать iterator j = i ; ++j, потому что i+1он не работает на итераторе, но это правильная идея
бобобобо

3
@bobobobo, вы можете использовать m_CursorStack.erase(boost::next(i).base())с Boost. или в C ++ 11m_CursorStack.erase(std::next(i).base())
alfC

12

... или другой способ удалить этот элемент из списка?

Для этого требуется -std=c++11флаг (для auto):

auto it=vt.end();
while (it>vt.begin())
{
    it--;
    if (*it == pCursor) //{ delete *it;
        it = vt.erase(it); //}
}

Работает очарование :)
Ден-Джейсон

@GaetanoMendola: почему?
Слэшмаис

3
Кто вам гарантирует, что итераторы в списке заказаны?
Гаэтано Мендола

7

Забавно, что на этой странице пока нет правильного решения. Итак, следующее является правильным:

В случае прямого итератора решение является прямым:

std::list< int >::iterator i = myList.begin();
while ( ; i != myList.end(); ) {
  if ( *i == to_delete ) {
    i = myList.erase( i );
  } else {
    ++i;
  } 
}

В случае обратного итератора вам нужно сделать то же самое:

std::list< int >::reverse_iterator i = myList.rbegin();
while ( ; i != myList.rend(); ) {
  if ( *i == to_delete ) {
    i = decltype(i)(myList.erase( std::next(i).base() ));
  } else {
    ++i;
  } 
}

Ноты:

  • Вы можете построить reverse_iteratorиз итератора
  • Вы можете использовать возвращаемое значение std::list::erase

Этот код работает, но, пожалуйста, объясните, почему используется next и как безопасно приводить прямой итератор к обратному итератору без разрушения мира
Lefteris E

1
@LefterisE Это не актерский состав. Создает новый обратный итератор из интегратора. Это нормальный конструктор обратного итератора.
Шимон Тот

3

Несмотря на то, что здесь используется метод reverse_iterator'' '' '' '' '' '' '' '' '']] base()и уменьшающий результат, стоит отметить, что reverse_iterators не имеют такой же статус, как обычные iterators. В общем, вы должны предпочесть обычные iterators reverse_iterators (а также const_iterators и const_reverse_iterators), именно по таким причинам. Посмотрите Журнал Доктора Доббса для всестороннего обсуждения почему.


3
typedef std::map<size_t, some_class*> TMap;
TMap Map;
.......

for( TMap::const_reverse_iterator It = Map.rbegin(), end = Map.rend(); It != end; It++ )
{
    TMap::const_iterator Obsolete = It.base();   // conversion into const_iterator
    It++;
    Map.erase( Obsolete );
    It--;
}

3

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

std::set<int> set{1,2,3,4,5};

for (auto itr = set.rbegin(); itr != set.rend(); )
{    
    if (*itr == 3)
    {
        auto it = set.erase(--itr.base());
        itr = std::reverse_iterator(it);            
    }
    else
        ++itr;
}

2

Если вам не нужно стирать все по ходу дела, то для решения проблемы вы можете использовать идиому удаления-удаления:

m_CursorStack.erase(std::remove(m_CursorStack.begin(), m_CursorStack.end(), pCursor), m_CursorStack.end());

std::removeМеняет местами все элементы в контейнере, которые соответствуют pCursorконцу, и возвращает итератор к первому элементу соответствия. Затем eraseиспользование диапазона сотрет с первого совпадения и пойдет до конца. Порядок несовпадающих элементов сохраняется.

Это может сработать быстрее для вас, если вы используете std::vector, где удаление в середине содержимого может потребовать большого количества копирования или перемещения.

Или, конечно, ответы выше, объясняющие использование reverse_iterator::base(), интересны и их стоит знать, чтобы решить поставленную задачу, я бы сказал, что std::removeона лучше подходит.


1

Просто хотел кое-что прояснить: в некоторых из приведенных выше комментариев и ответов портативная версия для стирания упоминается как (++ i) .base (). Однако, если я что-то упускаю, правильное утверждение (++ ri) .base () означает, что вы «увеличиваете» обратный итератор (а не итератор).

Я столкнулся с необходимостью сделать что-то подобное вчера, и этот пост был полезен. Спасибо всем.


0

Чтобы дополнить ответы других и потому что я наткнулся на этот вопрос во время поиска std :: string без особого успеха, здесь идет ответ с использованием std :: string, std :: string :: erase и std :: reverse_iterator

Моя проблема заключалась в удалении файла изображения из полной строки имени файла. Первоначально она была решена с помощью std :: string :: find_last_of, но я исследую альтернативный способ с помощью std :: reverse_iterator.

std::string haystack("\\\\UNC\\complete\\file\\path.exe");
auto&& it = std::find_if( std::rbegin(haystack), std::rend(haystack), []( char ch){ return ch == '\\'; } );
auto&& it2 = std::string::iterator( std::begin( haystack ) + std::distance(it, std::rend(haystack)) );
haystack.erase(it2, std::end(haystack));
std::cout << haystack;  ////// prints: '\\UNC\complete\file\'

Это использует алгоритм, итератор и заголовки строк.


0

Обратный итератор довольно сложен в использовании. Так что просто использовал общий итератор. 'r' Это начинается с последнего элемента. Когда найдешь что стереть. сотри его и верни следующий итератор. например, при удалении 3-го элемента он будет указывать текущий 4-й элемент. и новый 3-й. Таким образом, это должно быть уменьшено на 1, чтобы двигаться влево

void remchar(string& s,char c)
{      
    auto r = s.end() - 1;
    while (r >= s.begin() && *r == c)
    {
        r = s.erase(r);
        r -= 1;
    }
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.