web-dev-qa-db-fra.com

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". Quelqu'un peut-il me donner un exemple de quand une double évaluation se produit et pourquoi c'est mauvais?

P.S .: Étonnamment, je n'ai trouvé aucune explication détaillée lors de la recherche sur Google, sauf pour un exemple dans Clojure (que je ne peux pas comprendre).

48
AnkithD

Imaginez que vous écriviez ceci:

#define Max(a,b) (a < b ? b : a)

int x(){ turnLeft();   return 0; }
int y(){ turnRight();  return 1; }

puis l'a appelé comme ceci:

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

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

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

Après avoir évalué la condition x() < y(), le programme prend alors la branche requise entre y() : x(): dans notre cas true, qui appelle y() pour le = deuxième fois. Voir ( Live On Coliru .

Autrement dit, en passant un expression comme argument à votre macro de type fonction , Max évaluera potentiellement cette expression deux fois, car l'expression sera répétée où jamais le paramètre macro qu'il prend, est utilisé dans la définition de la macro. N'oubliez pas 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 que cela peut être fait efficacement en utilisant des modèles de fonction

PS: C++ a une fonction de modèle std::max .

68
WhiZTiM

a et b se produisent deux fois dans la définition de 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);

renverra 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 de telles macros comme max.

24
Franck

Considérez l'expression suivante:

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

Foo et Bar sont comme ceci:

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

int Bar()
{
   global_var++;
   return global_var;
}

Puis dans l'expression max d'origine est développée comme:

 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 modifie l'état du programme plus que le nombre de fois prévu. Dans mon exemple simple Bar, il ne renvoie pas la même valeur de manière cohérente.

20
selbie

Le langage macro en C et C++ est traité par un analyseur dédié au stade du "prétraitement"; les jetons sont traduits et la sortie est ensuite introduite dans le flux d'entrée de l'analyseur proprement dit. Les jetons #define Et #include Ne sont pas reconnus par les analyseurs C ou C++ eux-mêmes.

Ceci est important car cela signifie que lorsqu'une macro est dite "développée", cela signifie littéralement que. Donné

#define MAX(A, B) (A > B ? A : B)

int i = 1, j = 2;
MAX(i, j);

ce que l'analyseur C++ voit est

(i > j ? i : j);

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

MAX(i++, ++j);

est étendu à

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

Si nous passons quelque chose qui fait un appel de fonction:

MAX(f(), g());

cela s'é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, il devra appeler deux fois f() et g(), par exemple:

#include <iostream>

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 appelions une fonction extern, l'optimiseur peut ne pas pouvoir éviter d'appeler la fonction deux fois .

7
kfsone