Boucle basée sur une plage C ++ 11: obtenir l'élément par valeur ou référence à const

215

En lisant quelques exemples de boucles basées sur une plage, ils suggèrent deux façons principales 1 , 2 , 3 , 4

std::vector<MyClass> vec;

for (auto &x : vec)
{
  // x is a reference to an item of vec
  // We can change vec's items by changing x 
}

ou

for (auto x : vec)
{
  // Value of x is copied from an item of vec
  // We can not change vec's items by changing x
}

Bien.

Lorsque nous n'avons pas besoin de changer les vecéléments, l'OMI, les exemples suggèrent d'utiliser la deuxième version (par valeur). Pourquoi ils ne suggèrent pas quelque chose qui fait constréférence (au moins, je n'ai trouvé aucune suggestion directe):

for (auto const &x : vec) // <-- see const keyword
{
  // x is a reference to an const item of vec
  // We can not change vec's items by changing x 
}

N'est-ce pas mieux? N'évite-t-il pas une copie redondante à chaque itération alors que c'est un const?

masoud
la source

Réponses:

390

Si vous ne voulez pas modifier les éléments et éviter de faire des copies, alors auto const &c'est le bon choix:

for (auto const &x : vec)

Celui qui vous propose d'utiliser auto &est faux. Ignore les.

Voici un récapitulatif:

  • Choisissez auto xquand vous souhaitez travailler avec des copies.
  • Choisissez auto &xquand vous souhaitez travailler avec des éléments originaux et pouvez les modifier.
  • Choisissez auto const &xquand vous souhaitez travailler avec des éléments originaux et ne les modifierez pas.
Nawaz
la source
21
Merci pour la bonne réponse. Je suppose qu'il convient également de souligner que cela const auto &xéquivaut à votre troisième choix.
smg
7
@mloskot: C'est équivalent. (et qu'entendez-vous par «mais c'est la même syntaxe» ? La syntaxe est remarquablement différente.)
Nawaz
14
Manquant: auto&&lorsque vous ne voulez pas faire de copie inutile, et que vous vous souciez de la modifier ou non, et que vous voulez simplement travailler.
Yakk - Adam Nevraumont
4
La distinction de référence s'applique-t-elle aux copies si vous travaillez uniquement avec des types fondamentaux comme int / double?
4
@racarate: Je ne peux pas commenter la vitesse globale , et je pense que personne ne le peut, sans le profiler au préalable. Franchement, je ne baserai pas mon choix sur la vitesse, plutôt sur la clarté du code. Si je veux l'immuabilité, je l'utiliserais constà coup sûr. Cependant, que ce soit auto const &ou auto const peu de différence. Je choisirais auto const &juste d'être plus cohérent.
Nawaz
23

Si vous avez un std::vector<int>ou std::vector<double>, alors c'est très bien d'utiliser auto(avec copie de valeur) au lieu de const auto&, car copier un intou un doubleest bon marché:

for (auto x : vec)
    ....

Mais si vous avez un std::vector<MyClass>, où MyClassa une sémantique de copie non triviale (par exemple std::string, une classe personnalisée complexe, etc.), je suggère d'utiliser const auto&pour éviter les copies profondes :

for (const auto & x : vec)
    ....
Mr.C64
la source
3

Lorsque nous n'avons pas besoin de modifier les vecéléments, les exemples suggèrent d'utiliser la première version.

Ensuite, ils donnent une fausse suggestion.

Pourquoi ils ne suggèrent pas quelque chose qui fait référence

Parce qu'ils donnent une mauvaise suggestion :-) Ce que vous mentionnez est correct. Si vous voulez seulement observer un objet, il n'est pas nécessaire de créer une copie, et il n'est pas nécessaire d'avoir une non constréférence à lui.

ÉDITER:

Je vois que les références que vous liez fournissent toutes des exemples d'itération sur une plage de intvaleurs ou un autre type de données fondamental. Dans ce cas, étant donné que la copie d'un intn'est pas coûteuse, la création d'une copie équivaut essentiellement (sinon plus efficacement) à une observation const &.

Ce n'est cependant pas le cas en général pour les types définis par l'utilisateur. Les UDT peuvent être coûteux à copier, et si vous n'avez pas de raison de créer une copie (telle que la modification de l'objet récupéré sans altérer l'original), il est préférable d'utiliser a const &.

Andy Prowl
la source
1

Je vais être contraire ici et dire qu'il n'y a pas besoin d' auto const &une plage basée sur une boucle. Dites-moi si vous pensez que la fonction suivante est idiote (pas dans son but, mais dans la façon dont elle est écrite):

long long SafePop(std::vector<uint32_t>& v)
{
    auto const& cv = v;
    long long n = -1;
    if (!cv.empty())
    {
        n = cv.back();
        v.pop_back();
    }
    return n;
}

Ici, l'auteur a créé une référence const và utiliser pour toutes les opérations qui ne modifient pas v. C'est idiot, à mon avis, et le même argument peut être avancé pour utiliser auto const &comme variable dans une plage basée sur boucle au lieu auto &.

Benjamin Lindley
la source
3
@BenjaminLindley: Je peux donc en déduire que vous vous opposeriez également const_iteratorà une boucle for? Comment vous assurez-vous que vous ne modifiez pas les éléments d'origine d'un conteneur lorsque vous l'itérez?
Nawaz
2
@BenjaminLindley: Pourquoi votre code ne compilerait-il pas sans const_iterator? Laissez-moi deviner, le conteneur sur lequel vous itérez est const. Mais pourquoi est-ce constpour commencer? Quelque part que vous utilisez constpour vous assurer de quoi?
Nawaz
8
L'avantage de créer des objets constest comme les avantages de l'utilisation de private/ protectedin classes. Cela évite d'autres erreurs.
masoud
2
@BenjaminLindley: Oh, j'ai oublié ça. Dans ce cas, la mise en œuvre est également idiote. Mais si cette fonction ne se modifiait pas v, la mise en œuvre irait bien à l'OMI. Et si la boucle ne modifie jamais les objets qu'elle parcourt, il me semble juste d'avoir une référence const. Je vois votre point cependant. Le mien est que la boucle représente une unité de code à peu près comme le fait la fonction, et dans cette unité, il n'y a aucune instruction qui doit modifier la valeur. Par conséquent, vous pouvez «étiqueter» l'unité entière comme const, comme vous le feriez avec une constfonction membre.
Andy Prowl
1
Donc, vous pensez const &que la plage est stupide, car vous avez écrit un exemple imaginaire non pertinent d'un programmeur imaginaire qui a fait quelque chose de stupide avec la qualification cv ? ... OK alors
underscore_d