Borrar () un elemento en un vector no funciona

10

Tengo un vector Necesito eliminar los últimos 3 elementos en él. Describió esta lógica. El programa se bloquea. ¿Cuál podría ser el error?

vector<float>::iterator d = X.end();
    for (size_t i = 1; i < 3; i++) {
        if (i == 1) X.erase(d);
        else X.erase(d - i);
    }
dbUser11
fuente
El asesino aquí dno existe realmente. Es el valor canario de un pasado que se puede usar solo para encontrar el final de la vector. No puedes eliminarlo. Luego, tan pronto como borre un iterador, desaparecerá. No puede usarlo con seguridad después para nada, incluso d - i.
user4581301

Respuestas:

9

Si hay al menos 3 elementos en el vector, eliminar los últimos 3 elementos es simple: solo use pop_back 3 veces:

#include <vector>
#include <iostream>

int main() 
{
    std::vector<float> v = { 1, 2, 3, 4, 5 };
    for (int i = 0; i < 3 && !v.empty(); ++i)
       v.pop_back();

    for ( const auto &item : v ) std::cout << item << ' ';
        std::cout << '\n';
}

Salida:

1 2
PaulMcKenzie
fuente
11

Es un comportamiento indefinido pasar el end()iterador a la erase()sobrecarga de 1 parámetro . Incluso si no fuera así, erase()invalida los iteradores que están "en y después" del elemento especificado, lo que ddeja de ser válido después de la iteración del primer bucle.

std::vectortiene una erase()sobrecarga de 2 parámetros que acepta un rango de elementos para eliminar. No necesita un bucle manual en absoluto:

if (X.size() >= 3)
    X.erase(X.end()-3, X.end());

Demo en vivo

Remy Lebeau
fuente
3

Primero, X.end()no devuelve un iterador al último elemento del vector, sino que devuelve un iterador al elemento pasado el último elemento del vector, que es un elemento que el vector no posee, por eso cuando intentas borrarlo conX.erase(d) los bloqueos del programa.

En cambio, siempre que el vector contenga al menos 3 elementos, puede hacer lo siguiente:

X.erase( X.end() - 3, X.end() );

Que en su lugar va al tercer último elemento, y borra cada elemento después de eso hasta que llegue X.end().

EDITAR: Solo para aclarar, X.end()es un LegacyRandomAccessIterator que se especifica que tiene una -operación válida que devuelve otro LegacyRandomAccessIterator .

Nikko77
fuente
2

La definición de end()from cppreference es:

Devuelve un iterador que hace referencia al elemento pasado al final en el contenedor del vector.

y ligeramente debajo:

No apunta a ningún elemento y, por lo tanto, no debe ser desreferenciado.

En otras palabras, el vector no tiene ningún elemento al que apunte end (). Al desreferenciar ese no elemento a través del método erase (), posiblemente esté alterando la memoria que no pertenece al vector. Por lo tanto, a partir de ahí pueden pasar cosas feas.

Es la convención habitual de C ++ describir los intervalos como [bajo, alto], con el valor "bajo" incluido en el intervalo y el valor "alto" excluido del intervalo.

jpmarinier
fuente
2

Podrías usar un reverse_iterator:

#include <iostream>
#include <vector>

using namespace std;

int main()
{
    vector<float> X = {1.1, 2.2, 3.3, 4.4, 5.5, 6.6};

    // start the iterator at the last element
    vector<float>::reverse_iterator rit = X.rbegin();

    // repeat 3 times
    for(size_t i = 0; i < 3; i++)
    {
        rit++;
        X.erase(rit.base());
    }

    // display all elements in vector X
    for(float &e: X)
        cout << e << '\n';

    return 0;
}

Hay pocas cosas para mencionar:

  • reverse_iterator ritcomienza en el último elemento de la vector X. Esta posición se llama rbegin.
  • eraserequiere clásico iteratorpara trabajar. Obtenemos eso ritllamando base. Pero ese nuevo iterador apuntará al siguiente elemento desde ritadelante.
  • Es por eso que avanzamos ritantes de llamar baseyerase

Además, si desea obtener más información reverse_iterator, le sugiero que visite esta respuesta .

usuario desinfectado
fuente
2

Un comentario (ahora eliminado) en la pregunta decía que "no hay operador para un iterador". Sin embargo, el siguiente código compila y funciona en ambos MSVCy clang-cl, con el estándar establecido en C++17o C++14:

#include <iostream>
#include <vector>

int main()
{
    std::vector<float> X{ 1.1f, 2.2f, 3.3f, 4.4f, 5.5f, 6.6f };
    for (auto f : X) std::cout << f << ' '; std::cout << std::endl;
    std::vector<float>::iterator d = X.end();
    X.erase(d - 3, d);  // This strongly suggest that there IS a "-" operator for a vector iterator!
    for (auto f : X) std::cout << f << ' '; std::cout << std::endl;
    return 0;
}

La definición proporcionada para el operator-es la siguiente (en el <vector>encabezado):

    _NODISCARD _Vector_iterator operator-(const difference_type _Off) const {
        _Vector_iterator _Tmp = *this;
        return _Tmp -= _Off;
    }

Sin embargo, ciertamente no soy un abogado de lenguaje C ++, y es posible que esta sea una de esas extensiones 'peligrosas' de Microsoft. Me interesaría saber si esto funciona en otras plataformas / compiladores.

Adrian Mole
fuente
2
Creo que es válido, ya que los iteradores de un vector son de acceso aleatorio y -están definidos para esos tipos de iteradores.
PaulMcKenzie
@PaulMcKenzie De hecho, el analizador estático clang (que puede ser bastante estricto con los estándares) no dio ninguna advertencia al respecto.
Adrian Mole
1
Incluso si no hubiera operator-definido para los iteradores, podría usar std::advance()o en su std::prev()lugar.
Remy Lebeau
1

Esta declaración

    if (i == 1) X.erase(d);

tiene un comportamiento indefinido

Y esta declaración intenta eliminar solo el elemento anterior al último elemento

    else X.erase(d - i);

porque tienes un bucle con solo dos iteraciones

for (size_t i = 1; i < 3; i++) {

Necesitas algo como lo siguiente.

#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>

int main() 
{
    std::vector<float> v = { 1, 2, 3, 4, 5 };

    auto n = std::min<decltype( v.size() )>( v.size(), 3 ); 
    if ( n ) v.erase( std::prev( std::end( v ), n ), std::end( v ) );

    for ( const auto &item : v ) std::cout << item << ' ';
    std::cout << '\n';

    return 0;
}

La salida del programa es

1 2 
Vlad de Moscú
fuente