Quelle est la bonne manière de l'aide de C++11 de gamme pour?
Quelle est la bonne manière de l'aide de C++11 de for
?
Ce que la syntaxe doit être utilisé? for (auto elem : container)
,
ou for (auto& elem : container)
ou for (const auto& elem : container)
?
Ou une autre?
- Même considération s'applique que pour les arguments de la fonction.
- En fait, cela n'a que peu à voir avec la gamme pour les. La même chose peut être dit de toute
auto (const)(&) x = <expr>;
. - Cela a beaucoup à voir avec la gamme pour, bien sûr! Envisager un débutant qui voit plusieurs syntaxes et ne peut pas choisir le formulaire à utiliser. Le point de "Q&a" pour tenter de faire la lumière, et d'expliquer les différences de certains cas (et de discuter de cas qui compile bien, mais sont en quelque sorte des inefficace en raison inutile profonde des copies, etc.).
- Pour autant que je suis concerné, cela a plus à voir avec
auto
, en général, qu'avec la gamme de base; vous pouvez parfaitement utiliser basés sur la plage, sans qu'aucuneauto
!for (int i: v) {}
est parfaitement bien. Bien sûr, la plupart des points que vous soulevez dans votre réponse peut avoir plus à voir avec le type qu'avecauto
... mais à partir de la question, il n'est pas clair d'où la douleur est à point. Personnellement, je vie pour la suppression desauto
de la question; ou peut-être le rendre plus explicite le fait que si vous utilisezauto
ou explicitement le nom, le type, la question est concentrée sur la valeur de référence. - Je suis ouvert pour modifier le titre ou de modifier la question sous quelque forme que peut faire plus clair... Encore une fois, mon objectif était de discuter de plusieurs options pour la gamme à base de syntaxes (montrer le code qui compile mais est inefficace, le code qui ne parvient pas à compiler, etc.) et en essayant d'offrir quelques conseils à quelqu'un (surtout au niveau débutant) en approchant de C++11 gamme à base de boucles.
- Je comprends que (maintenant), ma seule suggestion est de ne pas utiliser
auto
. Lors de l'enseignement du nouveau matériel, il est plus facile d'enseigner les concepts à la fois. Vous pouvez ensuite ré-introduireauto
plus tard dans la réponse (par exemple, dans votre dernier chapitre sur le générique de code).
Vous devez vous connecter pour publier un commentaire.
Commençons la différenciation entre observation les éléments dans le continer
vs modifiant en place.
En observant les éléments
Prenons un exemple simple:
Le code ci-dessus imprime les éléments (
int
s) dans levector
:Considérons maintenant un autre cas, dans lequel les éléments du vecteur sont pas seulement de simples entiers,
mais des cas de plus en plus complexes, de la classe, avec le custom constructeur de copie, etc.
Si nous utilisons la
for (auto x : v) {...}
syntaxe avec cette nouvelle classe:la sortie est quelque chose comme:
Comme il peut être lu à partir de la sortie, constructeur de copie les appels sont effectués au cours de
gamme pour les itérations de boucle.
C'est parce que nous sommes capture les éléments du conteneur en valeur
(le
auto x
partie dansfor (auto x : v)
).C'est inefficace code, par exemple, si ces éléments sont des instances de
std::string
,tas les allocations de mémoire qui peut être fait, avec des déplacements coûteux pour le gestionnaire de mémoire, etc.
C'est inutile si nous voulons juste observer les éléments dans un conteneur.
Donc, une meilleure syntaxe est disponible: capture par
const
référence, c'est à direconst auto&
:Maintenant la sortie est:
Sans aucune fausse (et potentiellement coûteux) constructeur de copie d'appel.
Donc, quand observation éléments dans un conteneur (par exemple, pour un accès en lecture seule),
la syntaxe suivante est très bien pour de simples pas cher à copier types, comme
int
,double
, etc.:D'autre, la capture par
const
de référence est le meilleur dans le cas général,pour éviter d'inutiles (et potentiellement coûteux) constructeur de copie d'appels:
Modifier les éléments dans le conteneur
Si nous voulons modifier les éléments dans un conteneur à l'aide de gamme à base de
for
,le ci-dessus
for (auto elem : container)
etfor (const auto& elem : container)
les syntaxes sont mauvais.
En fait, dans le premier cas,
elem
magasins un copie de l'originalélément, de sorte que les modifications apportées à celui-ci sont juste perdus et ne sont pas stockées de façon persistante
dans le conteneur, par exemple:
Le résultat est juste la séquence initiale:
Au lieu de cela, une tentative de utilisant
for (const auto& x : v)
juste ne peut pas compiler.g++ renvoie un message d'erreur à quelque chose comme ceci:
La bonne approche dans ce cas est la capture par des non-
const
référence:La sortie est (comme prévu):
Ce
for (auto& elem : container)
syntaxe fonctionne aussi pour les types plus complexes,par exemple, en considérant un
vector<string>
:la sortie est:
Le cas particulier de proxy itérateurs
Supposons que nous avons une
vector<bool>
, et nous voulons inverser la logique booléenne étatde ses éléments, en utilisant la syntaxe ci-dessus:
Le code ci-dessus ne peut pas compiler.
g++ renvoie un message d'erreur semblable à ceci:
Le problème est que
std::vector
modèle est spécialisé pourbool
, avec unla mise en œuvre que packs la
bool
s pour optimiser l'espace (chaque valeur de type boolean eststockées dans un bit, huit "boolean" bits dans un octet).
À cause de cela (puisqu'il n'est pas possible de retourner une référence à un seul bit),
vector<bool>
utilise ce qu'on appelle "proxy itérateur" modèle.Un "proxy itérateur" est un itérateur qui, lorsque déréférencé, ne pas le rendement d'une
ordinaire
bool &
, mais au lieu de cela renvoie (en valeur) d'un objet temporaire,qui est un spécialisation sur Wikipedia">de la classe proxy convertibles à
boolean
.(Voir aussi ::la référence ne renvoie pas de référence à bool?">cette question et les réponses connexes ici sur StackOverflow.)
À modifier les éléments en place de
vector<bool>
, un nouveau type de syntaxe (à l'aide deauto&&
)doit être utilisé:
Le code suivant fonctionne:
et sorties:
Noter que le
for (auto&& elem : container)
syntaxe fonctionne également dans l'autre casd'ordinaire (sans proxy) itérateurs (par exemple, pour un
vector<int>
ou unvector<string>
).(Comme une note de côté, cette "observation" de la syntaxe de
for (const auto& elem : container)
fonctionne très bien aussi pour le proxy itérateur cas.)Résumé
La discussion ci-dessus peuvent être résumées dans le guide-lignes:
Pour observation les éléments, utilisez la syntaxe suivante:
Si les objets sont à bas prix pour copier (comme
int
s,double
s, etc.),il est possible d'utiliser une forme légèrement simplifiée:
Pour modifiant les éléments en place, utilisation:
Si le conteneur utilise "proxy itérateurs" (comme
std::vector<bool>
), utilisation:Bien sûr, si il ya un besoin de faire un copie locale de l'élément à l'intérieur du corps de la boucle, la capture de en valeur (
for (auto elem : container)
) est un bon choix.Notes supplémentaires sur les génériques de code
Dans code générique, puisque nous ne pouvons pas faire des hypothèses sur le type générique
T
être pas cher, à copier, à observation mode, il est sûr de toujours utiliserfor (const auto& elem : container)
.(Ce ne sera pas déclencher potentiellement coûteux des copies inutiles, fonctionne très bien aussi pour pas cher-pour-copie des types comme
int
, et aussi pour les récipients à l'aide de proxy-les itérateurs, commestd::vector<bool>
.)En outre, dans modifiant mode, si nous voulons code générique de travailler également en cas de procuration-les itérateurs, la meilleure option est
for (auto&& elem : container)
.(Cela fonctionne très bien aussi pour les conteneurs à l'aide d'ordinaire non-proxy-les itérateurs, comme
std::vector<int>
oustd::vector<string>
.)Donc, dans code générique, les lignes directrices suivantes peuvent être fournies:
Pour observation les éléments, utilisation:
Pour modifiant les éléments en place, utilisation:
auto&&
? Est-il unconst auto&&
?auto&&
, car il couvreauto&
tout aussi bien.std::vector<bool>
me donnait cryptique des erreurs quand il est allé à travers mes binaire streaming code: votre explication m'a aidé à travailler autour d'elle - ty!auto&&
. N'est-ce pas la sémantique de déplacement, c'est à dire ne serait-ce pas dire que les éléments dans le conteneur sont déplacés? Si oui, ne serait-ce pas laisser le contenant dans un état non valide?&&
est un "ingrédient" pour déplacer la sémantique, mais utilisé dans le contexte ci-dessus, il ne déplace pas les éléments du conteneur (en la laissant dans un état non valide). Dans plusieurs cas, pense, par exemple, de déplacer ctors, même si vous avez&&
dansX(X&& other)
, vous devez être explicite dans l'appel destd::move()
à "voler les tripes" deother
. De toute façon, vous pouvez ouvrir une nouvelle question à ce sujet (je pense que c'est mieux que de discuter dans les commentaires). Merci encore!auto &&
déclare un "univers de référence", comme Scott Meyers appelle. Recommandée lire!Il n'y a pas de façon correcte à utiliser
for (auto elem : container)
, oufor (auto& elem : container)
oufor (const auto& elem : container)
. Vous venez d'exprimer ce que vous souhaitez.Permettez-moi d'insister sur ce point. Nous allons faire une promenade.
C'est un sucre syntaxique pour:
Vous pouvez l'utiliser si votre conteneur contient des éléments qui ne sont pas chers à copier.
C'est un sucre syntaxique pour:
Utilisez cette fonction lorsque vous souhaitez écrire sur les éléments dans le conteneur directement, par exemple.
C'est un sucre syntaxique pour:
Que le commentaire dit, juste pour la lecture. Et c'est à ce sujet, tout ce qui est "correct" lorsqu'il est utilisé correctement.
Le bon moyen est toujours
Cela permettra de garantir la préservation de l'ensemble de la sémantique.
auto const &
de faire mon intention est-elle claire?const
, et ils ne sont jamais mutable. De toute façon, ma réponse vous est oui, je le ferais.Alors que la motivation initiale de la gamme-pour la boucle aurait été facilité d'itération sur les éléments d'un conteneur, la syntaxe est suffisamment générique pour être utile, même pour les objets qui ne sont pas purement des conteneurs.
Syntaxiques exigence pour la boucle for, c'est que
range_expression
soutienbegin()
etend()
comme des fonctions, soit en tant que membre des fonctions du type qu'il donne ou non des fonctions de membre du ce prendre un exemple de ce type.Comme un exemple artificiel, on peut générer une gamme de nombres et d'effectuer une itération sur la plage à l'aide de la classe suivante.
Avec le suivant
main
fonction,l'on pourrait obtenir la sortie suivante.