7
votes

Modification d'un conteneur dans STD :: for_ach

Est-ce que la norme interdit explicitement la modification d'un conteneur dans std :: for_ach code>?

Plus spécifiquement, dans le cas de std :: Liste code> Les itérateurs ne sont pas invalidés lorsque le la liste est modifiée. Ainsi, le code suivant fonctionne: P>

std::list<int> list;

list.push_front(5);
list.push_front(10);
auto it = list.end();
it--; // point to 5

std::for_each(list.begin(), list.end(), [&](int i){
    /* the line below will remove the last element in the list;
     * list will have only one element (the currently processed one);
     * list.end() is not invalidated and we exit for_each() */
    list.erase(it);
});


1 commentaires

Cette question devrait être beaucoup plus perçue, vraiment ...


5 Réponses :


-1
votes

Oui, c'est légal. Il y a des iters constants si vous avez besoin de quelque chose qui est garanti pour ne pas modifier le conteneur sous-jacent.


2 commentaires

Preuve? Si ce n'est pas un std :: Liste ou je supprime un itérateur à l'élément actuellement traité, il va se bloquer.


Alors s'il vous plaît définir "légal". Je voulais dire: ce code est-il conforme à la norme ou fonctionne-t-il juste par accident?



2
votes

Votre code est limite légal.

avec le code posté, il est légal par coïncidence. Si vous ajoutez un élément de plus à la liste, votre programme s'écrasera. P>

Essayez ce qui suit pour voir le problème: P>

int main()
{
   std::list<int> list;

   list.push_front(5);
   list.push_front(10);
   list.push_front(12);
   auto it = list.end();
   it--; // point to 5

   std::for_each(list.begin(), list.end(), [&](int i){
                 /* the line below will remove the last element in the list;
                  * list will have only one element (the currently processed one);
                  * list.end() is not invalidated and we exit for_each() */
                 list.erase(it);
                 });
}


6 commentaires

Oui, cela effacera le même itérateur deux fois, ce qui n'est pas autorisé. Mais la question reste-t-elle conceptuellement autorisée à modifier le conteneur ou à déclencher une sorte de UB?


Il est ub d'appeler deux fois en utilisant le même itérateur car le premier appel à effacer rend l'itérateur invalide.


Oui, mais ce n'est pas ce que je demande. Supposons, il est assuré que l'itérateur n'est pas effacé deux fois. Est-ce toujours un UB?


@Kane, dans ce cas, je ne suivons pas votre question. Le point clé est que c'est que l'UB est d'utiliser un itérateur qui a été effacé. Si votre code n'utilise pas un itérateur après avoir été effacé, il n'est pas valide et ne devrait pas aboutir à UB.


Il ne s'agit pas seulement de mon code n'utilisant pas d'itérateur après une invalidité. Il s'agit également de std :: for_ach ne l'utilise pas après une invalidité.


@Kane, je m'impliquais ça. std :: for_ach appelez le dernier argument avec chaque valeur dans la plage [premier, dernier) . Sauf si vous appelez Erase sur l'itérateur actuel que std :: for_ach utilise, vous devriez être ok.



3
votes

Est-ce que la norme interdit explicitement de modifier un conteneur dans std :: for_ach ?

La seule chose que je peux penser que ce code ne serait pas conforme standard, c'est que dans [alg.foreach] nous avons

complexité: s'applique f exactement dernier - premier fois.

f être la fonction pour_ach s'applique.

Étant donné que la liste est modifiée et qu'un élément est supprimé, nous ne répondons plus à cette complexité. Je ne sais pas si cela le rend non conforme mais c'est la seule chose que je pouvais voir que cela ne vous permettrait pas de supprimer des éléments du conteneur lors de l'utilisation de pour_ache


0 commentaires

3
votes

Est-ce que la norme interdit explicitement la modification d'un conteneur dans std :: for_ach ?

Non, pas explicitement . Au moins pour toute définition raisonnable de "explicitement". La section sur std :: for_ach (§25.2.4) ne dit rien du tout sur l'invalidation des itérateurs ou les effets secondaires causant la modification de la plage itératée.

Il y a cependant de nombreuses raisons implicites pourquoi votre code ne peut pas fonctionner. Considérez comment std :: for_ach doit être mis en œuvre. C'est complètement générique et ne sait donc rien sur le std :: list il fonctionne; Vous avez donné deux itérateurs à former une plage, ce qui signifie que dernier est accessible depuis premier . Comment std :: for_ach sache que votre objet fonction a invalidé l'objet Itérateur local avec lequel il passe de d'abord à Dernier

Et même si la fonction savait que l'invalidation s'est produite, que devrait-il faire? Dans une boucle écrite à la main, vous obtiendrez le résultat de effacer et continuez l'itération avec elle, mais std :: for_ach ne peut pas faire cela, par définition.

Voici un article de référence ancien mais toujours valide: http: / /www.drdobbs.com/effective-standard-c-library-foreach-vs/184403769

Il dit:

Par exemple, invalidez les itérateurs ou les séquences que la L'algorithme fonctionne avec est désastreuse dans tous les cas. Même la fonction objet fourni à destination de_ach doit obéir à cette règle de bon sens, même si La norme ne le dit pas.


Paraphraser quelques mots sages, j'ai une fois lu:

la norme C ++ est écrite par des êtres humains; Ce n'est pas toujours aussi parfait que possible.


0 commentaires

2
votes

25.2.4 / 2:

Effets: applique F au résultat de la déséroférance de chaque itérateur de la GAMME [PREMIER, DERNIER), à partir d'une première et d'une procédure au dernier - 1.

alors 25.2.4 / 4:

Complexité: Applique exactement f EXO exactement - première fois.

Je suis prêt à faire valoir que ces deux points interdisent explicitement la mutation du conteneur d'une manière qui change le nombre d'éléments.

qui dit, même ce n'est pas votre exemple échoue si la liste a même un élément de plus, ou si votre implémentation de la bibliothèque standard augmente l'itérateur avant d'appeler votre fonction (et je ne vois rien dans la norme qui interdit de telle une implémentation).


0 commentaires