web-dev-qa-db-fra.com

Existe-t-il une meilleure alternative à std :: remove_if pour supprimer des éléments d'un vecteur?

La tâche de supprimer des éléments avec une certaine propriété d'un std::vector ou un autre conteneur se prête à une implémentation de style fonctionnel: pourquoi se soucier des boucles, de la désallocation de mémoire et du déplacement correct des données?

Cependant, la manière standard de le faire en C++ semble être l'idiome suivant:

std::vector<int> ints;
...
ints.erase(
    std::remove_if(ints.begin(), 
                   ints.end(),
                   [](int x){return x < 0;}),
    ints.end());

Cet exemple supprime tous les éléments inférieurs à zéro d'un vecteur entier.

Je le trouve non seulement laid mais aussi facile à utiliser incorrectement. Il est clair que std::remove_if ne peut pas changer la taille du vecteur (comme son nom l'indique) car il ne fait passer que les itérateurs. Mais de nombreux développeurs, dont moi-même, ne comprennent pas cela au début.

Existe-t-il donc une manière plus sûre et, espérons-le, plus élégante d'y parvenir? Sinon, pourquoi?

25
Frank Puffer

Je le trouve non seulement laid mais aussi facile à utiliser incorrectement.

Ne vous inquiétez pas, nous l'avons tous fait au début.

Il est clair que std :: remove_if ne peut pas changer la taille du vecteur (comme son nom l'indique) car il ne fait passer que les itérateurs. Mais de nombreux développeurs, dont moi-même, ne comprennent pas cela au début.

Même. Cela déroute tout le monde. Il n'aurait probablement pas dû s'appeler remove_if il y a toutes ces années. Rétrospectivement, hein?

Existe-t-il donc une manière plus sûre et, espérons-le, plus élégante d'y parvenir?

Non

Sinon, pourquoi?

Parce que c'est le moyen le plus sûr et le plus élégant qui préserve les performances lors de la suppression d'éléments d'un conteneur dans lequel la suppression d'un élément invalide les itérateurs.

anticipant:

Tout ce que je peux faire?

Oui, enveloppez cet idiome dans une fonction

template<class Container, class F>
auto erase_where(Container& c, F&& f)
{
    return c.erase(std::remove_if(c.begin(), 
                                  c.end(),
                                  std::forward<F>(f)),
                   c.end());    
}

L'appel dans l'exemple motivant devient alors:

auto is_negative = [](int x){return x < 0;};
erase_where(ints, is_negative);

ou

erase_where(ints, [](int x){return x < 0;});
25
Richard Hodges

Cela deviendra bientôt disponible dans un compilateur prêt pour C++ 17 via std::experimental::erase_if algorithme:

#include <algorithm>
#include <iostream>
#include <iterator>
#include <vector>
#include <experimental/vector>

int main()
{
    std::vector<int> ints { -1, 0, 1 };   
    std::experimental::erase_if(ints, [](int x){
        return x < 0;
    });
    std::copy(ints.begin(), ints.end(), std::ostream_iterator<int>(std::cout, ","));
}

Exemple en direct qui imprime 0,1

14
TemplateRex