Cómo llamar a borrar con un iterador inverso

181

Estoy tratando de hacer algo como esto:

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

Sin embargo, borrar toma un iterador y no un iterador inverso. ¿Hay alguna forma de convertir un iterador inverso en un iterador regular u otra forma de eliminar este elemento de la lista?

0xC0DEFACE
fuente
17
Por otro lado, cuando escriba bucles como estos, no calcule repetidamente el iterador final como lo hace aquí i != m_CursorStack.rend(). En cambio, escribe i = m_CursorStack.rbegin(), end = m_CursorStack.rend(); i != end;. Es decir, inicialice un iterador que pueda mantener para comparar repetidamente, suponiendo que la posición final no cambiará como un efecto secundario de su cuerpo de bucle.
seh
Me parece que la pregunta obvia aquí sería por qué estás haciendo esto. ¿Qué gana al recorrer la lista al revés? ¿Qué gana escribiendo este código usted mismo en lugar de usarlo std::remove?
Jerry Coffin
¿Y sigue siendo válido un iterador en una lista std :: list después de que el elemento al que hace referencia se haya borrado?
Steve Jessop
3
Solo quiero eliminar 1 elemento, por lo tanto, 'break;', usando 'remove' eliminaría cualquier coincidencia que tarde más y no haga lo que quiero. El elemento que quiero eliminar en este caso particular casi siempre será el final de la lista o muy cerca de él, por lo que iterar en reversa también es más rápido y se adapta mejor al problema.
0xC0DEFACE
44
stackoverflow.com/a/2160581/12386 Esto dice que los diseñadores específicamente no definen la implementación porque se supone que usted, como usuario, no debe saber ni preocuparse, pero @seh arriba espera que mágicamente sepamos que rend () se calcula y caro.
stu

Respuestas:

181

Después de más investigación y pruebas encontré la solución. Aparentemente de acuerdo con el estándar [24.4.1 / 1] la relación entre i.base () e i es:

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

(de un artículo del Dr. Dobbs ):

texto alternativo

Por lo tanto, debe aplicar un desplazamiento al obtener la base (). Por lo tanto, la solución es:

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

EDITAR

Actualización para C ++ 11.

reverse_iterator no iha cambiado:

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

reverse_iterator ies avanzado:

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

Esto me parece mucho más claro que mi solución anterior. Use lo que necesite.

0xC0DEFACE
fuente
27
Debería tomar nota de un poco más del artículo que citó: para ser portátil, la expresión debería ser m_CursorStack.erase( (++i).base())(hombre, hacer esto con iteradores inversos me duele la cabeza ...). También debe tenerse en cuenta que el artículo DDJ se incorpora al libro "Efectivo STL" de Meyer.
Michael Burr
8
Encuentro ese diagrama más confuso que útil. Desde rbegin, RI y rend son en realidad señalando el elemento a la derecha de lo que se sienten atraídos por estar apuntados. El diagrama muestra a qué elemento accedería si lo hiciera *, pero estamos hablando de a qué elemento apuntaría si fuera a baseellos, que es un elemento a la derecha. No soy tan fanático de las soluciones --(i.base())o (++i).base()ya que mutan el iterador. Prefiero (i+1).base()que funcione también.
mgiuca
44
Los iteradores inversos son mentirosos ... cuando se hace referencia, un iterador inverso devuelve el elemento anterior . Ver aquí
bobobobo
44
Para ser absolutamente claro, esta técnica todavía no se puede utilizar en un bucle normal (donde el iterador se incrementa de la manera normal). Ver stackoverflow.com/questions/37005449/…
logidelic
1
m_CursorStack.erase ((++ i) .base ()) parece que sería un problema si ++ te hiciera pasar el último elemento. ¿Puedes llamar a borrar on end ()?
stu
16

Tenga en cuenta que m_CursorStack.erase( (++i).base())puede ser un problema si se usa en un forbucle (consulte la pregunta original) porque cambia el valor de i. La expresión correcta esm_CursorStack.erase((i+1).base())

Andrey
fuente
44
Tendría que crear una copia del iterador y hacerlo iterator j = i ; ++j, porque i+1no funciona en un iterador, pero esa es la idea correcta
bobobobo
3
@bobobobo, puedes usar m_CursorStack.erase(boost::next(i).base())con Boost. o en C ++ 11m_CursorStack.erase(std::next(i).base())
alfC
12

... u otra forma de eliminar este elemento de la lista?

Esto requiere la -std=c++11bandera (para auto):

auto it=vt.end();
while (it>vt.begin())
{
    it--;
    if (*it == pCursor) //{ delete *it;
        it = vt.erase(it); //}
}
slashmais
fuente
Funciona a las mil maravillas :)
Den-Jason
@GaetanoMendola: ¿por qué?
slashmais
3
¿Quién le garantiza que se ordenan los iteradores en una lista?
Gaetano Mendola
7

Es curioso que todavía no haya una solución correcta en esta página. Entonces, el siguiente es el correcto:

En el caso del iterador directo, la solución es sencilla:

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

En caso de iterador inverso, debe hacer lo mismo:

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;
  } 
}

Notas:

  • Puedes construir un reverse_iteratordesde un iterador
  • Puede usar el valor de retorno de std::list::erase
Gaetano Mendola
fuente
Este código funciona, pero explique por qué se usa el siguiente y cómo es seguro lanzar un iterador directo a un iterador inverso sin que el mundo colapse
Lefteris E
1
@LefterisE Eso no es un elenco. Crea un nuevo iterador inverso a partir de un interador. Este es el constructor normal del iterador inverso.
Šimon Tóth
3

Si bien usar el método reverse_iterator's base()y decrementar el resultado funciona aquí, vale la pena señalar que reverse_iteratorno se les da el mismo estado que los iterators normales . En general, debe preferir los iterators a reverse_iterators regulares (así como a const_iterators y const_reverse_iterators), precisamente por razones como esta. Vea el Diario del Doctor Dobbs para una discusión en profundidad de por qué.

Adam Rosenfield
fuente
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--;
}
Nismo
fuente
3

Y aquí está el fragmento de código para convertir el resultado de borrar de nuevo a un iterador inverso para borrar un elemento en un contenedor mientras se itera en el reverso. Un poco extraño, pero funciona incluso al borrar el primer o el último elemento:

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;
}
etham
fuente
2

Si no necesita borrar todo a medida que avanza, para resolver el problema, puede usar el idioma borrar-eliminar:

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

std::removeintercambia todos los elementos en el contenedor que coinciden pCursorhasta el final y devuelve un iterador al primer elemento coincidente. Luego, el eraseuso de un rango se borrará de la primera partida e irá al final. Se conserva el orden de los elementos que no coinciden.

Esto podría funcionar más rápido si está utilizando un std::vector, donde borrar en medio del contenido puede implicar muchas copias o movimientos.

O, por supuesto, las respuestas anteriores que explican el uso de reverse_iterator::base()son interesantes y vale la pena saber, para resolver el problema exacto enunciado, diría que std::removeencaja mejor.

gavinbeatty
fuente
1

Solo quería aclarar algo: en algunos de los comentarios y respuestas anteriores, la versión portátil para borrar se menciona como (++ i) .base (). Sin embargo, a menos que me falte algo, la declaración correcta es (++ ri) .base (), lo que significa que 'incrementa' el reverse_iterator (no el iterador).

Me encontré con la necesidad de hacer algo similar ayer y esta publicación fue útil. Gracias a todos.

usuario1493570
fuente
0

Para complementar las respuestas de otros y debido a que me topé con esta pregunta mientras buscaba sobre std :: string sin mucho éxito, aquí va una respuesta con el uso de std :: string, std :: string :: erase y std :: reverse_iterator

Mi problema fue borrar el nombre de archivo de una imagen de una cadena de nombre de archivo completa. Originalmente se resolvió con std :: string :: find_last_of, pero investigo una forma alternativa con 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\'

Esto utiliza algoritmos, iteradores y encabezados de cadena.

fmmarques
fuente
0

El iterador inverso es bastante difícil de usar. Así que solo usé el iterador general. 'r' Comienza desde el último elemento. Cuando encuentre algo para borrar. bórrelo y vuelva al siguiente iterador. Por ejemplo, cuando elimine el tercer elemento, señalará el cuarto elemento actual. y nuevo 3er. Por lo tanto, debe reducirse 1 para moverse a la izquierda

void remchar(string& s,char c)
{      
    auto r = s.end() - 1;
    while (r >= s.begin() && *r == c)
    {
        r = s.erase(r);
        r -= 1;
    }
}
Mark Yang
fuente