Qu’est-ce qu’une double évaluation et pourquoi devrait-elle être évitée?

Je lisais cela en C ++ en utilisant des macros comme

#define max(a,b) (a > b ? a : b) 

peut entraîner une “double évaluation”. Est-ce que quelqu’un peut me donner un exemple d’une double évaluation et pourquoi c’est mauvais?

PS: Etonnamment, je n’ai pas trouvé d’explication détaillée sur Google, à l’exception de Clojure (que je ne comprends pas).

Imaginez que vous avez écrit ceci:

 #define Max(a,b) (a < b ? b : a) int x(){ turnLeft(); return 0; } int y(){ turnRight(); return 1; } 

alors appelé comme ceci:

 auto var = Max(x(), y()); 

Savez-vous que turnRight() sera exécuté deux fois? Cette macro, Max étendra à:

 auto var = (x() < y() ? y() : x()); 

Après avoir évalué la condition x() < y() , le programme prend alors la twig requirejse entre y() : x() : dans notre cas, true , qui appelle y() pour la deuxième fois . Voyez-le vivre sur Coliru .

En termes simples, en passant une expression comme argument à votre macro de type fonction , Max évaluera potentiellement cette expression deux fois, car l'expression sera répétée là où le paramètre de macro qu'elle utilise est utilisé dans la définition de la macro. Rappelez-vous que les macros sont gérées par le préprocesseur .


Donc, en fin de compte, n'utilisez pas de macros pour définir une fonction (en fait une expression dans ce cas) simplement parce que vous voulez qu’elle soit générique , alors qu’elle peut être efficacement réalisée à l’aide de modèles de fonction.

PS: C ++ a une fonction de template std::max .

a et b apparaissent deux fois dans la définition de la macro. Donc, si vous l’utilisez avec des arguments qui ont des effets secondaires, les effets secondaires sont exécutés deux fois.

 max(++i, 4); 

retournera 6 si i = 4 avant l’appel. Comme ce n’est pas le comportement attendu, vous devriez préférer les fonctions en ligne pour remplacer ces macros comme max .

Considérons l’expression suivante:

  x = max(Foo(), Bar()); 

Foo et Bar sont comme ça:

 int Foo() { // do some complicated code that takes a long time return result; } int Bar() { global_var++; return global_var; } 

Ensuite, dans le max origine, l’expression est développée comme suit:

  Foo() > Bar() ? Foo() : Bar(); 

Dans les deux cas, Foo ou Bar va être exécuté deux fois. Cela prend plus de temps que nécessaire ou change l’état du programme plus que le nombre de fois prévu. Dans mon exemple simple de Bar , il ne renvoie pas la même valeur de manière cohérente.

Le langage macro en C et C ++ est traité par un parsingur dédié au stade du pré-traitement. les jetons sont traduits et la sortie est ensuite introduite dans le stream d’entrée de l’parsingur proprement dit. #define jetons #define et #include ne sont pas reconnus par les parsingurs C ou C ++ eux-mêmes.

Ceci est important car cela signifie que lorsqu’une macro est dite “étendue”, cela signifie littéralement cela. Donné

 #define MAX(A, B) (A > B ? A : B) int i = 1, j = 2; MAX(i, j); 

ce que voit l’parsingur C ++ est

 (i > j ? i : j); 

Cependant, si nous utilisons la macro avec quelque chose de plus complexe, la même extension se produit:

 MAX(i++, ++j); 

est étendu à

 (i++ > ++j ? i++ : ++j); 

Si on passe quelque chose qui fait un appel de fonction:

 MAX(f(), g()); 

cela étendra à

 (f() > g() ? f() : g()); 

Si le compilateur / optimiseur peut démontrer que f() n’a pas d’effets secondaires, il traitera cela comme

 auto fret = f(); auto gret = g(); (fret > gret) ? fret : gret; 

Si ce n’est pas le cas, alors il devra appeler f () et g () deux fois, par exemple:

 #include  int f() { std::cout << "f()\n"; return 1; } int g() { std::cout << "g()\n"; return 2; } #define MAX(A, B) (A > B ? A : B) int main() { MAX(f(), g()); } 

Démo en direct: http://ideone.com/3JBAmF

De même, si nous extern une fonction extern , l’optimiseur pourrait ne pas pouvoir appeler la fonction deux fois .