std:: remove avec vector:: erase et comportement indéfini

Partout sur le web, Je vois des gens utiliser l'idiome erase / remove pour les vecteurs C++ comme ceci:

#include <vector> // the general-purpose vector container
#include <iostream>
#include <algorithm> // remove and remove_if
int main()
{
  // initialises a vector that holds the numbers from 0-9.
  std::vector<int> v = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

  // removes all elements with the value 5
  v.erase( std::remove( v.begin(), v.end(), 5 ), v.end() );

  return 0;
}

C'est-à-dire, si je veux effacer tous les éléments correspondant à certains critères (par exemple le nombre 5 d'un vecteur de int s), alors j'utilise std::remove ou std::remove_if en conjonction avec vector.erase comme ceci:

vector.erase( std::remove( vector.begin(), vector.end(), <some_value>), vector.end());

Cela fonctionne bien en général; std::remove (et remove_if) copie (ou l'utilisation d'une sémantique de déplacement dans C++11) les éléments qui doivent être supprimés à la fin du vecteur, le vecteur de notre l'exemple précédent ressemblera maintenant à ceci:

{ 0, 1, 2, 3, 4, 6, 7, 8, 9, 5 };

Avec l'élément 5 en gras parce qu'il a été déplacé à la fin.

Maintenant, std::remove retourne un itérateur pour elle, que nous utilisons ensuite dans erase pour effacer les éléments. Beau.

Mais qu'en est-il de l'exemple suivant?

int main()
{
  // initialises an empty vector.
  std::vector<int> v = {};

  // removes all elements with the value 5
  v.erase( std::remove( v.begin(), v.end(), 5 ), v.end() );

  return 0;
}

Cela semble fonctionner comme prévu (ne rien effacer, ne pas segmenter, etc.) sur toutes les plates-formes, Je l'exécute sur, mais je sais que juste parce que quelque chose fonctionne, ne signifie pas que ce n'est pas un comportement indéfini.

Rapide référence pour vector.erase, dit ceci (l'emphase est mienne):

iterator erase (const_iterator first, const_iterator last);

first, last sont

Itérateurs spécifiant une plage dans le vecteur] à supprimer: [first,last). c'est-à-dire que la plage comprend tous les éléments compris entre first et last, y compris l'élément pointé par le premier mais pas celui pointé par last. Les types de membres iterator et const_iterator sont aléatoires accédez aux types d'itérateurs qui pointent vers des éléments.

Est Donc vector.erase(vector.end(),vector.end()) comportement indéfini?

Voici ce que dit la référence rapide sur la sécurité des exceptions:

Si les éléments supprimés incluent le dernier élément dans le conteneur, aucune exception n'est lancée (garantie de non-lancer). Sinon, le conteneur est garanti pour se terminer dans un état valide (garantie de base). Un position ou range invalide provoque un comportement indéfini.

Donc, la réponse, au moins pour moi semble être "oui", et cette réponse StackOverflow semble le soutenir.

Par conséquent, l'idiome commun est-il faux?

En supposant que c'est un comportement indéfini, alors tout appel à {[25] } pourrait renvoyer un itérateur à {[26] } qui devrait être vérifié avant d'appeler vector.erase, et appeler remove sur un vecteur vide semble revenir vector.end: (IDEOne pour le code ci-dessous )

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

int main() {
   vector<int> myInts;
   auto anIter = std::remove(myInts.begin(),myInts.end(),5);
   if (anIter == myInts.end())
      std::cout << "iterator = myInts.end()";
}

Enfin, ma question:

L'idiome supprimer/effacer réel devrait-il être cette?

auto endOfRangeIterator = std::remove(vector.begin(), vector.end(), <value>);
if (endOfRangeIterator != vector.end())
   vector.erase(endOfRangeIterator, vector.end())
27
demandé sur Community 2014-05-20 17:31:37

3 réponses

24.2.1/7 la plupart des modèles algorithmiques de la bibliothèque qui fonctionnent sur des structures de données ont des interfaces qui utilisent des plages. Une gamme est une paire des itérateurs qui désignent le début et la fin du calcul. une plage [i,i) est une plage vide ; en général, une plage [i,j) fait référence aux éléments de la structure de données commençant par l'élément pointé par i et jusqu'à l'élément pointé par j.

Emphase mine.

De Plus, la description de erase vous citez n'est pas le texte normatif de la norme. La norme a ceci à dire (tableau 100):

a.erase(q1,q2)

Effets: Efface les éléments de l'intervalle [t1, t2).

Cela ne nécessite pas que q1 soit déréférencable. Si [q1, q2) est une plage vide (par 24.2.1 / 7), alors aucun élément n'est dans la plage, et donc aucun n'est effacé.

27
répondu Igor Tandetnik 2014-05-20 13:43:19

Tout comme le vecteur.effacer(vecteur.end(),d'un vecteur.end()) comportement indéfini?

Non. En raison de la déclaration juste à côté de celui que vous avez souligné:

Itérateurs spécifiant une plage dans le vecteur] à supprimer: [premier, dernier). c'est-à-dire que la plage comprend tous les éléments entre le premier et le dernier, y compris l'élément pointé par le premier mais pas celui pointé par le dernier.

Donc, vector.erase(vector.end(),vector.end()) n'essaie pas d'effacer vector.end() car il est pointé par le paramètre last.

Certes, cette définition est ambiguë et ces déclarations peuvent être interprétées comme contradictoires. Le libellé Cité n'est pas utilisé par la norme.

4
répondu user2079303 2014-05-20 13:53:46

Je pense que le plus important dans votre citation est:

Itérateurs spécifiant une plage dans le vecteur] à supprimer: [first,last). c'est-à-dire que la plage comprend tous les éléments entre le premier et dernier, y compris l'élément pointé par first mais pas celui pointé par last . Les types de membres iterator et const_iterator sont aléatoires accédez aux types d'itérateurs qui pointent vers des éléments.

Comme nous l'avons constaté dans les commentaires, cette citation de cpluspluc.com est incorrect. Cela ne violera pas les règles dans le cas de ( v.end, v.end) mais sera incorrect dans le cas de

#include <vector>

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

    v.erase( v.begin(), v.begin());
}

Parce que la déclaration qui se contredit avec

La plage comprend (...), , y compris l'élément pointé par v. begin () mais pas celui pointé par v. begin ().

Ne peut pas être une instruction valide.

Norme C++ n3337 in § 23.2.2 le tableau 100 spécifie que

a.erase(q1,q2) retourne iterator . Et la note est:

Nécessite: pour vector et deque, T doit être MoveAssignable. Effet: Efface les éléments de l'intervalle [t1, t2).

Et c'est ce qu'il dit à propos de la gamme [i,j) dans § 24.2.1/7 Itérateur exigences

La plupart des modèles algorithmiques de la bibliothèque qui fonctionnent sur des données les structures ont des interfaces qui utilisent des plages. Une gamme est une paire de itérateurs qui désignent le début et la fin du calcul. Un la plage [i,i) est une plage vide; en général, une plage [i, j) fait référence à la éléments de la structure de données commençant par l'élément pointé par i, et jusqu'à mais ne comprenant pas l'élément pointé par j. L'intervalle [i,j) est valide si et seulement si j est accessible à partir de I. le résultat du l'application de fonctions dans la bibliothèque à des plages non valides est indéterminé.

Ainsi, pour répondre à vos questions

, Mais que dire de l' l'exemple suivant?

Cplusplus.com est faux dans ce cas

Tout comme le vecteur.effacer(vecteur.end(),d'un vecteur.end()) comportement indéfini?

Non, aucun comportement indéfini n'est déclenché.

Par conséquent, l'idiome commun est-il faux?

Non, c'est exact.

L'idiome supprimer/effacer réel devrait-il être celui-ci?

Il n'y a pas besoin de cela, mais c'est aussi bien.

4
répondu 4pie0 2014-05-20 15:05:30