L'effacement () d'un élément dans un vecteur ne fonctionne pas

10

J'ai un vecteur. J'ai besoin de supprimer les 3 derniers éléments. Décrit cette logique. Le programme se bloque. Quelle pourrait être l'erreur?

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
la source
Le tueur ici dn'existe pas vraiment. C'est la valeur canari un-la-fin utilisable uniquement pour trouver la fin de la vector. Vous ne pouvez pas le supprimer. Ensuite, dès que vous effacez un itérateur, il a disparu. Vous ne pouvez pas l'utiliser en toute sécurité par la suite pour quoi que ce soit, y compris d - i.
user4581301

Réponses:

9

S'il y a au moins 3 éléments dans le vecteur, supprimer les 3 derniers éléments est simple - utilisez simplement pop_back 3 fois:

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

Production:

1 2
PaulMcKenzie
la source
11

Il est un comportement indéfini de passer l' end()itérateur à la erase()surcharge à 1 paramètre . Même s'il ne l'était pas, erase()invalide les itérateurs qui sont "au niveau et après" l'élément spécifié, ce qui le rend dinvalide après la 1ère itération de boucle.

std::vectora une erase()surcharge à 2 paramètres qui accepte une gamme d'éléments à supprimer. Vous n'avez pas du tout besoin d'une boucle manuelle:

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

Démo en direct

Rémy Lebeau
la source
3

Tout d'abord, X.end()ne renvoie pas un itérateur au dernier élément du vecteur, il renvoie plutôt un itérateur à l'élément après le dernier élément du vecteur, qui est un élément que le vecteur ne possède pas réellement, c'est pourquoi lorsque vous essayez de effacez-le avec X.erase(d)le programme plante.

À la place, à condition que le vecteur contienne au moins 3 éléments, vous pouvez effectuer les opérations suivantes:

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

Ce qui va à la place à l'avant-dernier élément et efface chaque élément après cela jusqu'à ce qu'il arrive X.end().

EDIT: Juste pour clarifier, X.end()est un LegacyRandomAccessIterator qui est spécifié pour avoir une -opération valide qui renvoie un autre LegacyRandomAccessIterator .

Nikko77
la source
2

La définition de end()from cppreference est:

Renvoie un itérateur faisant référence à l'élément past-the-end dans le conteneur vectoriel.

et légèrement en dessous:

Il ne pointe vers aucun élément et ne doit donc pas être déréférencé.

En d'autres termes, le vecteur ne contient aucun élément vers lequel pointe () pointe. En déréférençant ce non-élément via la méthode erase (), vous modifiez éventuellement la mémoire qui n'appartient pas au vecteur. Des choses laides peuvent donc arriver à partir de là.

C'est la convention C ++ habituelle de décrire les intervalles comme [bas, haut), avec la valeur "basse" incluse dans l'intervalle et la valeur "haute" exclue de l'intervalle.

jpmarinier
la source
2

Vous pouvez utiliser 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;
}

Il y a peu de choses à mentionner:

  • reverse_iterator ritcommence au dernier élément du vector X. Cette position est appeléerbegin .
  • erasenécessite un classique iteratorpour travailler avec. Nous obtenons cela riten appelant base. Mais ce nouvel itérateur pointera vers l'élément suivant derit avant.
  • C'est pourquoi nous avançons ritavant d'appeler baseeterase

Aussi, si vous voulez en savoir plus reverse_iterator, je vous suggère de visiter cette réponse .

sanitizedUser
la source
2

Un commentaire (maintenant supprimé) dans la question a déclaré qu '"il n'y a pas - d'opérateur pour un itérateur". Cependant, le code suivant compile et fonctionne dans les deux MSVCet clang-cl, avec la norme définie sur C++17ou 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 définition fournie pour le operator-est la suivante (dans l'en- <vector>tête):

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

Cependant, je ne suis certainement pas un avocat du langage C ++, et il est possible que ce soit l'une de ces extensions Microsoft «dangereuses». Je serais très intéressé de savoir si cela fonctionne sur d'autres plateformes / compilateurs.

Adrian Mole
la source
2
Je pense que c'est valide, car les itérateurs d'un vecteur sont à accès aléatoire et -sont définis pour ces types d'itérateurs.
PaulMcKenzie
@PaulMcKenzie En effet - l'analyseur statique de clang (qui peut être assez strict avec les normes) n'a donné aucun avertissement à ce sujet.
Adrian Mole
1
Même s'il n'y avait pas de operator-définition pour les itérateurs, vous pouvez simplement utiliser std::advance()ou à la std::prev()place.
Remy Lebeau
1

Cette déclaration

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

a un comportement indéfini.

Et cette déclaration essaie de supprimer uniquement l'élément avant le dernier élément

    else X.erase(d - i);

parce que vous avez une boucle avec seulement deux itérations

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

Vous avez besoin de quelque chose comme ce qui suit.

#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 sortie du programme est

1 2 
Vlad de Moscou
la source