web-dev-qa-db-fra.com

La boucle "for" basée sur la plage déprécie-t-elle de nombreux algorithmes simples?

Solution d'algorithme:

std::generate(numbers.begin(), numbers.end(), Rand);

Solution for-loop basée sur la gamme:

for (int& x : numbers) x = Rand();

Pourquoi voudrais-je utiliser le plus verbeux std::generate sur des boucles basées sur une plage en C++ 11?

81
fredoverflow

La première version

std::generate(numbers.begin(), numbers.end(), Rand);

nous indique que vous souhaitez générer une séquence de valeurs.

Dans la deuxième version, le lecteur devra le découvrir lui-même.

Économiser sur la frappe est généralement sous-optimal, car il est le plus souvent perdu en temps de lecture. La plupart du code est lu beaucoup plus qu'il n'est tapé.

78
Bo Persson

Que la boucle for soit basée sur une plage ou non ne fait aucune différence, elle simplifie uniquement le code entre parenthèses. Les algorithmes sont plus clairs en ce qu'ils montrent le intention.

Personnellement, ma première lecture de:

std::generate(numbers.begin(), numbers.end(), Rand);

est "nous attribuons à tout dans une plage. La plage est numbers. Les valeurs attribuées sont aléatoires".

Ma première lecture de:

for (int& x : numbers) x = Rand();

est "nous faisons quelque chose pour tout dans une plage. La plage est numbers. Ce que nous faisons est d'attribuer une valeur aléatoire."

Ce sont sacrément similaires, mais pas identiques. Une raison plausible que je pourrais vouloir provoquer la première lecture, c'est parce que je pense que le fait le plus important sur ce code est qu'il attribue à la plage. Il y a donc votre "pourquoi voudrais-je ...". J'utilise generate car en C++ std::generate Signifie "affectation de plage". Comme le fait btw std::copy, La différence entre les deux est ce que vous attribuez.

Il existe cependant des facteurs confondants. Les boucles for basées sur une plage ont une manière intrinsèquement plus directe d'exprimer que la plage est numbers, que les algorithmes basés sur un itérateur. C'est pourquoi les gens travaillent sur des bibliothèques d'algorithmes basées sur des plages: boost::range::generate(numbers, Rand); est plus belle que la version std::generate.

Par contre, int& Dans votre boucle for basée sur une plage est une ride. Que se passe-t-il si le type de valeur de la plage n'est pas int, nous faisons ici quelque chose de subtilement ennuyeux qui dépend de sa conversion en int&, Tandis que le code generate dépend uniquement du retour de Rand étant assignable à l'élément. Même si le type de valeur est int, je pourrais quand même m'arrêter pour penser s'il l'est ou non. D'où auto, ce qui diffère de penser aux types jusqu'à ce que je vois ce qui est assigné - avec auto &x Je dis "prenez une référence à l'élément range, quel que soit le type qui pourrait avoir". De retour en C++ 03, les algorithmes (parce que ce sont des modèles de fonctions) étaient la façon de masquer les types exacts, maintenant ils sont a façon.

Je pense qu'il a toujours été le cas que les algorithmes les plus simples n'ont qu'un avantage marginal sur les boucles équivalentes. Les boucles basées sur la plage améliorent les boucles (principalement en supprimant la plupart du passe-partout, bien qu'il y en ait un peu plus que cela). Ainsi, les marges se resserrent et vous changez peut-être d'avis dans certains cas spécifiques. Mais il y a encore une différence de style.

30
Steve Jessop

À mon avis, l'article 43 de la STL efficace: "Préférez les appels d'algorithmes aux boucles écrites à la main." est toujours un bon conseil.

J'écris généralement des fonctions wrapper pour me débarrasser de l'enfer begin()/end(). Si vous faites cela, votre exemple ressemblera à ceci:

my_util::generate(numbers, Rand);

Je crois qu'il bat la plage basée sur la boucle à la fois dans communiquer l'intention et dans la lisibilité.


Cela dit, je dois admettre qu'en C++ 98, certains appels d'algorithme STL ont produit un code inexprimable et que "Préférer les appels d'algorithme aux boucles manuscrites" ne semblait pas être une bonne idée. Heureusement, les lambdas ont changé cela.

Prenons l'exemple suivant de Herb Sutter: Lambdas, Lambdas Everywhere .

Tâche: Trouver le premier élément dans v qui est > x et < y.

Sans lambdas:

auto i = find_if( v.begin(), v.end(),
bind( logical_and<bool>(),
bind(greater<int>(), _1, x),
bind(less<int>(), _1, y) ) );

Avec lambda

auto i=find_if( v.begin(), v.end(), [=](int i) { return i > x && i < y; } );
23
Ali

Dans mon avis, la boucle manuelle, bien que susceptible de réduire la verbosité, manque de manière lisible:

for (int& x : numbers) x = Rand();

Je n'utiliserais pas cette boucle pour initialiser1 la plage définie par nombres, car quand je la regarde, il me semble que c'est itération sur une plage de nombres, mais en réalité ce n'est pas le cas (en substance ), c'est-à-dire qu'au lieu de lecture de la plage, c'est écriture dans la plage.

L'intention est beaucoup plus claire lorsque vous utilisez std::generate.

1. initialiser dans ce contexte signifie donner significatif valeur aux éléments du conteneur.

22
Nawaz

Il y a certaines choses que vous ne pouvez pas faire (simplement) avec des boucles basées sur une plage que les algorithmes qui prennent les itérateurs en entrée peuvent. Par exemple avec std::generate:

Remplissez le conteneur jusqu'à limit (exclu, limit est un itérateur valide sur numbers) avec des variables d'une distribution et le reste avec des variables d'une autre distribution.

std::generate(numbers.begin(), limit, Rand1);
std::generate(limit, numbers.end(), Rand2);

Les algorithmes basés sur les itérateurs vous donnent un meilleur contrôle sur la plage sur laquelle vous opérez.

9
Khaur

Pour le cas particulier de std::generate, Je suis d'accord avec les réponses précédentes sur le problème de lisibilité/intention. std :: generate me semble une version plus claire. Mais j'avoue que c'est en quelque sorte une question de goût.

Cela dit, j'ai une autre raison de ne pas jeter l'algorithme std :: - il existe certains algorithmes spécialisés pour certains types de données.

L'exemple le plus simple serait std::fill. La version générale est implémentée comme une boucle for sur la plage fournie, et cette version sera utilisée lors de l'instanciation du modèle. Mais pas toujours. Par exemple. si vous lui fournissez une plage qui est un std::vector<int> - il appelle souvent memset sous le capot, ce qui donne un code beaucoup plus rapide et meilleur.

J'essaie donc de jouer une carte d'efficacité ici.

Votre boucle manuscrite peut être aussi rapide qu'une version d'algorithme std ::, mais elle peut difficilement être plus rapide. Et plus que cela, l'algorithme std :: peut être spécialisé pour des conteneurs et des types particuliers et cela se fait sous l'interface STL propre.

6
Sergei Nosov

Ma réponse serait peut-être et non. Si nous parlons de C++ 11, alors peut-être (plus comme non). Par exemple std::for_each est vraiment ennuyeux à utiliser même avec des lambdas:

std::for_each(c.begin(), c.end(), [&](ExactTypeOfContainedValue& x)
{
    // do stuff with x
});

Mais l'utilisation de la plage pour est bien meilleure:

for (auto& x : c)
{
    // do stuff with x
}

D'un autre côté, si nous parlons de C++ 1y, alors je dirais que non, les algorithmes ne seront pas obsolètes par plage basée sur. Dans le comité standard C++, il y a un groupe d'étude qui travaille sur une proposition pour ajouter des plages au C++, et il y a aussi des travaux sur les lambdas polymorphes. Les plages supprimeraient la nécessité d'utiliser une paire d'itérateurs et le lambda polymorphe vous permettrait de ne pas spécifier le type d'argument exact de lambda. Cela signifie que std::for_each pourrait être utilisé comme ceci (ne prenez pas cela comme un fait dur, c'est juste à quoi ressemblent les rêves aujourd'hui):

std::for_each(c.range(), [](x)
{
    // do stuff with x
});
3
lego

La boucle for basée sur une plage n'est que cela. Jusqu'à ce que la norme soit bien sûr modifiée.

L'algorithme est une fonction. Une fonction qui impose des exigences à ses paramètres. Les exigences sont formulées dans une norme pour permettre par exemple une implémentation qui tire parti de tous les threads d'exécution disponibles et vous accélérera automatiquement.

1
Tomek

Une chose à noter est qu'un algorithme exprime ce qui est fait, pas comment.

La boucle basée sur la plage comprend la façon dont les choses sont faites: commencez par le premier, appliquez et passez à l'élément suivant jusqu'à la fin. Même un algorithme simple pourrait faire les choses différemment (au moins quelques surcharges pour des conteneurs spécifiques, sans même penser au vecteur horrible), et au moins la façon dont cela est fait n'est pas l'affaire des écrivains.

Pour moi, c'est une grande partie de la différence, encapsulez autant que vous le pouvez, et cela justifie la phrase quand vous le pouvez, utilisez des algorithmes.

1
Cedric