web-dev-qa-db-fra.com

Effacer des éléments de unordered_map dans une boucle

Il y a plusieurs réponses sur StackOverflow qui suggèrent que la boucle suivante est un bon moyen d'effacer des éléments d'un std::unordered_map qui satisfont à un prédicat pred:

std::unordered_map<...> m;
auto it = m.begin();
while (it != m.end())
{
    if (pred(*it))
        it = m.erase(it);
    else
        ++it;
}

Je suis particulièrement intéressé par C++ 11 (par opposition à C++ 14), et la note inquiétante suivante sur cppreference.com suggère que la boucle ci-dessus dépend d'un comportement indéfini et peut ne pas fonctionner en C++. 11 après tout:

L'ordre des éléments non effacés est préservé (cela permet d'effacer des éléments individuels en itérant dans le conteneur) (depuis C++ 14)

Voir aussi Titre 2356. Stabilité de l'effacement dans des conteneurs associatifs non ordonnés qui contient le changement de formulation demandé en Working Draft N3797 élément 14 de la page 754 (la phrase supplémentaire commençant par ", et conserve l'ordre relatif ... ").

Cette formulation est relative à N3797.

Modifiez [unord.req], p14 comme indiqué:

Les membres insert et emplace ne doivent pas affecter la validité des références aux éléments de conteneur, mais peuvent invalider tous les itérateurs de Du conteneur. Les membres d'effacement n'invalident que les itérateurs et les références Aux éléments effacés, et préservent l'ordre relatif de les éléments qui ne sont pas effacés.

Si mon interprétation de la note de cppreference.com est correcte et que la boucle ci-dessus dépend d'un comportement indéfini en C++ 11, quel est le moyen le plus efficace de résoudre ce problème en C++ 11?

20
foxcub

Pour vous conformer à C++ 11, vous êtes malheureusement un peu limité dans la manière de résoudre ce problème. Vos options se résument essentiellement à:

  1. Parcourez le unordered_map et construisez une liste de clés à supprimer comme ceci:

    //std::unordered_map<...> mymap;
    std::vector<decltype(mymap)::key_type> vec;
    for (auto&& i : mymap)
        if (/*compare i*/)
            vec.emplace_back(i.first);
    for (auto&& key : vec)
        mymap.erase(key);
    
  2. Itérez sur l'objet et réinitialisez-le si nous trouvons quelque chose à supprimer - je ne le recommanderais vraiment que pour de petits ensembles de données. ceux d'entre vous qui pensent que goto est inconditionnellement mauvais, eh bien, cette option est discutable.

    //std::unordered_map<...> mymap;
    reset:
    for (auto&& i : mymap)
        if (/*compare i*/) {
            mymap.erase(i.first);
            goto reset;
        }
    
  3. En tant que quelque peu là-bas , vous pouvez simplement créer un nouveau unordered_map et déplacer les éléments que vous souhaitez conserver. C'est sans doute une bonne option lorsque vous avez plus à supprimer qu'à conserver.

    //std::unordered_map<...> mymap;
    decltype(mymap) newmap;
    for (auto&& i : mymap)
        if (/*i is an element we want*/)
            newmap.emplace(std::move(i));
    mymap.swap(newmap);
    
13
Olipro

Eh bien, vous pouvez toujours faire ceci:

std::unordered_map<...> m;
std::vector<key_type> needs_removing;
for(auto&& pair : m)
{
    if (pred(pair))
        needs_removing.Push_back(pair.first);
}
for(auto&& key : needs_removing)
    m.erase(key);

C'est plus lent, mais la complexité est la même.

1
Pubby