89 votes

Est-ce un piège connu de C ++11 pour les boucles?

Imaginons que nous avons une structure pour la tenue de 3 chambres doubles avec certaines fonctions de membre:

struct Vector {
  double x, y, z;
  // ...
  Vector &negate() {
    x = -x; y = -y; z = -z;
    return *this;
  }
  Vector &normalize() {
     double s = 1./sqrt(x*x+y*y+z*z);
     x *= s; y *= s; z *= s;
     return *this;
  }
  // ...
};

C'est un peu artificiel, pour des raisons de simplicité, mais je suis sûr que vous acceptez que le même code est là. Les méthodes vous permettent de chaîne, par exemple:

Vector v = ...;
v.normalize().negate();

Ou encore:

Vector v = Vector{1., 2., 3.}.normalize().negate();

Maintenant, si nous avons fourni begin() et end() fonctions, nous pourrions utiliser notre Vecteur dans un nouveau style pour la boucle, dire à de la boucle sur les 3 coordonnées x, y et z (vous pouvez sans aucun doute construire plus "utile" exemples de par le remplacement de Vecteur avec par exemple une Chaîne de caractères):

Vector v = ...;
for (double x : v) { ... }

Nous pouvons même le faire:

Vector v = ...;
for (double x : v.normalize().negate()) { ... }

et aussi:

for (double x : Vector{1., 2., 3.}) { ... }

Cependant, la suite (il me semble) est cassé:

for (double x : Vector{1., 2., 3.}.normalize()) { ... }

Alors qu'il semble comme une combinaison logique des deux précédents usages, je pense que ce dernier usage crée une balançant de référence, tandis que les deux précédentes sont tout à fait acceptable.

  • Est-ce correct et Largement apprécié?
  • La partie de la ci-dessus est la "mauvaise" partie, qui doit être évitée?
  • Serait la langue être améliorée en modifiant la définition de la gamme à base de boucle tels que temporaires construits dans l'expression existent pour la durée de la boucle?

64voto

Nicol Bolas Points 133791

Est-ce correct et Largement apprécié?

Oui, votre compréhension des choses est correcte.

La partie de la ci-dessus est la "mauvaise" partie, qui doit être évitée?

La mauvaise partie est de prendre un l valeur de référence à un temporaire renvoyée par une fonction, et de la lier à une r-valeur de référence. Il est tout aussi mauvais que cela:

auto &&t = Vector{1., 2., 3.}.normalize();

Le temporaire Vector{1., 2., 3.}s'durée de vie ne peut être prolongée, car le compilateur n'a aucune idée de la valeur de retour de normalize références.

Serait la langue être améliorée en modifiant la définition de la gamme à base de boucle tels que temporaires construits dans l'expression existent pour la durée de la boucle?

Que serait hautement incompatible avec la façon dont C++ fonctionne.

Serait-il éviter certaines erreurs faites par des gens à l'aide de enchaînés expressions temporaires ou différents paresseux-méthodes d'évaluation des expressions? Oui. Mais il aurait également besoin d'être spécial compilateur de code, ainsi que d'être confus quant à pourquoi il ne fonctionne pas avec d'autres constructions d'expression.

Beaucoup plus solution raisonnable serait d'une certaine façon à informer le compilateur que la valeur de retour d'une fonction est toujours une référence à l' this, et par conséquent, si la valeur de retour est lié à une entreprise de l'extension de construire, puis d'étendre le bon temporaire. C'est un langage de niveau de la solution.

Actuellement (si le compilateur prend en charge), vous pouvez faire en sorte qu' normalize ne peut pas être appelée sur un temporaire:

struct Vector {
  double x, y, z;
  // ...
  Vector &normalize() & {
     double s = 1./sqrt(x*x+y*y+z*z);
     x *= s; y *= s; z *= s;
     return *this;
  }
  Vector &normalize() && = delete;
};

Cela entraînera Vector{1., 2., 3.}.normalize() pour donner une erreur de compilation, alors que v.normalize() fonctionnera très bien. Évidemment, vous ne serez pas en mesure de faire corriger les choses comme ceci:

Vector t = Vector{1., 2., 3.}.normalize();

Mais vous aussi, vous ne serez pas en mesure de faire des choses incorrectes.

25voto

pour (double x : Vecteur{1., 2., 3.}.normalize()) { ... }

Ce n'est pas une limitation de la langue, mais un problème avec votre code. L'expression Vector{1., 2., 3.} crée un temporaire, mais l' normalize fonction retourne une lvalue de référence. Parce que l'expression est une lvalue, le compilateur suppose que l'objet sera en vie, mais parce que c'est une référence à un temporaire, l'objet meurt après la pleine expression est évaluée, si vous êtes de gauche avec une balançant de référence.

Maintenant, si vous changer votre conception de renvoyer un nouvel objet par valeur plutôt qu'une référence à l'objet courant, alors il n'y aurait pas de problème et le code ne fonctionnent pas comme prévu.

4voto

leftaroundabout Points 23679

À mon humble avis, le deuxième exemple est déjà faussée. Que la modification des opérateurs de retour *this est pratique dans la façon dont vous l'avez mentionné: il permet le chaînage de modificateurs. Il peut être utilisé pour un simple passage sur le résultat de la modification, mais ce n'est sujette à erreur, car il peut facilement être négligé. Si je vois quelque chose comme

Vector v{1., 2., 3.};
auto foo = somefunction1(v, 17);
auto bar = somefunction2(true, v, 2, foo);
auto baz = somefunction3(bar.quun(v), 93.2, v.qwarv(foo));

Je ne voudrais pas automatiquement suspect que les fonctions de modifier v comme un effet secondaire. Bien sûr, ils pourraient, mais il serait source de confusion. Donc, si je devais écrire quelque chose comme ça, je serais assurez-vous que v reste constante. Pour votre exemple, je voudrais ajouter des fonctions libres

auto normalized(Vector v) -> Vector {return v.normalize();}
auto negated(Vector v) -> Vector {return v.negate();}

et puis écrire les boucles

for( double x : negated(normalized(v)) ) { ... }

et

for( double x : normalized(Vector{1., 2., 3}) ) { ... }

C'est de l'OMI mieux lisible, et c'est plus sûr. Bien sûr, cela suppose une copie supplémentaire, cependant, pour des tas de données allouées cela pourrait probablement être fait dans un bon marché C++11 opération de déplacement.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X