La cancellazione () di un elemento in un vettore non funziona


10

Ho un vettore Devo eliminare gli ultimi 3 elementi in esso. Descritto questa logica. Il programma si arresta in modo anomalo. Quale potrebbe essere l'errore?

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

Il killer qui dnon esiste davvero. È il valore canarino un-passato-la-fine utilizzabile solo per trovare la fine del file vector. Non puoi rimuoverlo. Successivamente, non appena si cancella un iteratore, non c'è più. Non puoi usarlo in sicurezza in seguito per qualsiasi cosa, incluso d - i.
user4581301

Risposte:


9

Se ci sono almeno 3 elementi nel vettore, eliminare gli ultimi 3 elementi è semplice: basta usare pop_back 3 volte:

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

Produzione:

1 2

11

È un comportamento indefinito passare l' end()iteratore al erase()sovraccarico a 1 parametro . Anche se non lo fosse, erase()invalida gli iteratori che sono "in e dopo" l'elemento specificato, rendendo dnon valido dopo l'iterazione del 1 ° ciclo.

std::vectorha un erase()sovraccarico di 2 parametri che accetta un intervallo di elementi da rimuovere. Non è necessario un loop manuale:

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

Dimostrazione dal vivo


3

Innanzitutto, X.end()non restituisce un iteratore all'ultimo elemento del vettore, ma restituisce un iteratore all'elemento oltre l'ultimo elemento del vettore, che è un elemento che il vettore non possiede in realtà, ecco perché quando provi a cancellalo con X.erase(d)il programma si blocca.

Invece, a condizione che il vettore contenga almeno 3 elementi, è possibile effettuare le seguenti operazioni:

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

Che invece va al terzo ultimo elemento e cancella ogni elemento successivo fino a quando non arriva a X.end().

EDIT: solo per chiarire, X.end()è un LegacyRandomAccessIterator che è specificato per avere un'operazione valida -che restituisce un altro LegacyRandomAccessIterator .


2

La definizione di end()from cppreference è:

Restituisce un iteratore che fa riferimento all'elemento passato-fine nel contenitore vettoriale.

e leggermente sotto:

Non indica alcun elemento e pertanto non deve essere sottovalutato.

In altre parole, il vettore non ha elementi a cui punta end (). Dereferenziando quel non-elemento attraverso il metodo erase (), è possibile che si stia modificando la memoria che non appartiene al vettore. Da qui possono succedere brutte cose.

È la solita convenzione C ++ per descrivere gli intervalli come [basso, alto), con il valore "basso" incluso nell'intervallo e il valore "alto" escluso dall'intervallo.


2

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

Ci sono alcune cose da menzionare:

  • reverse_iterator ritinizia dall'ultimo elemento di vector X. Questa posizione è chiamata rbegin.
  • eraserichiede il classico iteratorcon cui lavorare. Lo capiamo ritchiamando base. Ma quel nuovo iteratore punterà all'elemento successivo da ritavanti.
  • Ecco perché avanziamo ritprima di chiamare baseeerase

Inoltre, se vuoi saperne di più reverse_iterator, ti suggerisco di visitare questa risposta .


2

Un commento (ora eliminato) nella domanda affermava che "non esiste un operatore per un iteratore". Tuttavia, il codice seguente viene compilato e funziona in entrambi MSVCe clang-cl, con lo standard impostato su 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 definizione fornita per il operator-è la seguente ( <vector>nell'intestazione):

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

Tuttavia, non sono certamente un avvocato del linguaggio C ++ ed è possibile che questa sia una di quelle "pericolose" estensioni di Microsoft. Sarei molto interessato a sapere se funziona su altre piattaforme / compilatori.


2
Penso che sia valido, poiché gli iteratori di un vettore sono un accesso casuale ed -è definito per quei tipi di iteratori.
PaulMcKenzie,

@PaulMcKenzie In effetti - l'analizzatore statico di clang (che può essere piuttosto rigoroso con gli standard) non ha dato alcun avvertimento al riguardo.
Adrian Mole,

1
Anche se non sono stati operator-definiti gli iteratori, è possibile semplicemente utilizzare std::advance()o std::prev()invece.
Remy Lebeau,

1

Questa dichiarazione

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

ha un comportamento indefinito.

E questa affermazione tenta di rimuovere solo l'elemento prima dell'ultimo elemento

    else X.erase(d - i);

perché hai un ciclo con solo due iterazioni

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

Hai bisogno di qualcosa di simile al seguente.

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

L'output del programma è

1 2 
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.