Joliesse de l'opérateur ternaire par rapport à l'instruction if

Je navigue à travers du code et j'y ai trouvé quelques opérateurs ternaires. Ce code est une bibliothèque que nous utilisons, et il est censé être assez rapide.

Je pense que si nous économisons quoi que ce soit, sauf pour l'espace là-bas.

Quelle est votre expérience?

24
demandé sur max 2010-11-16 11:21:53

5 réponses

Performance

L'opérateur ternaire ne doit pas différer en performance d'un équivalent bien écritif/else déclaration... ils peuvent bien se résoudre à la même représentation dans l'Arbre de syntaxe abstraite, subir les mêmes optimisations, etc..

Des choses que vous ne pouvez faire avec ? :

Si vous initialisez une constante ou une référence, ou si vous déterminez quelle valeur utiliser dans une liste d'initialisation de membre, alors if/else les instructions ne peuvent pas être utilisées mais ? : peut être:

const int x = f() ? 10 : 2;

X::X() : n_(n > 0 ? 2 * n : 0) { }

Factoring pour le code concis

Clés raisons d'utiliser ? : inclure la localisation, et éviter de répéter de manière redondante d'autres parties des mêmes instructions / appels de fonction, par exemple:

if (condition)
    return x;
else
    return y;

...est seulement préférable à...

return condition ? x : y;

...pour des raisons de lisibilité si vous traitez avec des programmeurs très inexpérimentés, ou certains des termes sont assez compliqués pour que le ? : la structure se perd dans le bruit. Dans les cas plus complexes comme:

fn(condition1 ? t1 : f1, condition2 ? t2 : f2, condition3 ? t3 : f3);

Équivalent if/else:

if (condition1)
    if (condition2)
        if (condition3)
            fn(t1, t2, t3);
        else
            fn(t1, t2, f3);
    else if (condition3)
            fn(t1, f2, t3);
        else
            fn(t1, f2, f3);
else
    if (condition2)
       ...etc...

C'est beaucoup d'appels de fonctions supplémentaires que le compilateur peut ou non optimiser.

Les temporaires nommés ne peuvent-ils pas améliorer la monstruosité if/else ci-dessus?

Si les expressions t1, f1, t2 etc. sont trop verbeux pour taper à plusieurs reprises, la création de temporaires nommés peut aider, mais alors:

  • Pour obtenir des performances correspondant ? : vous pouvez avoir besoin d'utiliser std::move, sauf lorsque le même temporary est passé à deux && paramètres dans la fonction appelée: alors vous devez l'éviter. C'est plus complexe et sujet aux erreurs.

  • c ? x : y évalue c alors, soit, mais pas les deux x et y, ce qui le rend sûr pour dire que le test d'un pointeur n'est pas nullptr avant de l'utiliser, tout en procurant une certaine valeur de repli et/ou du comportement. Code obtient uniquement les effets secondaires de celui de x et y est effectivement sélectionné. Avec les temporaires nommés, vous devrez peut-être if / else autour ou ? : à l'intérieur de leur initialisation à l'exécution de code indésirable, ou à l'exécution de code plus souvent que souhaité.

Différence fonctionnelle: type de résultat unificateur

Considérons:

void is(int) { std::cout << "int\n"; }
void is(double) { std::cout << "double\n"; }

void f(bool expr)
{
    is(expr ? 1 : 2.0);

    if (expr)
        is(1);
    else
        is(2.0);
}

Dans la version de l'opérateur conditionnel ci-dessus, 1 subit une Conversion Standard en double de sorte que le type correspondant 2.0, ce qui signifie que la surcharge is(double) est appelée même pour le true/1 situation. Le if/else l'instruction ne déclenche pas cette conversion:true/1 appels de branche is(int).

Vous ne pouvez pas non plus utiliser d'expressions avec un type global de void dans un opérateur conditionnel, alors qu'elles sont valides dans if/else.

Accent: valeur-sélection avant / après l'action nécessitant des valeurs

Il y a un autre mise en évidence:

An if/else statement met l'accent sur la ramification en premier et ce qui doit être fait est secondaire, tandis qu'un opérateur ternaire met l'accent sur ce qui doit être fait sur la sélection des valeurs pour le faire.

Dans différentes situations, l'un ou l'autre peut mieux refléter la perspective "naturelle" du programmeur sur le code et le rendre plus facile à comprendre, à vérifier et à maintenir. Vous pouvez vous retrouver en sélectionnant l'un sur l'autre en fonction de l'ordre dans lequel vous tenez compte de ces facteurs lors de l'écriture du code-si vous vous êtes lancé dans "faire quelque chose", vous pouvez utiliser l'une des deux valeurs (ou quelques-unes) pour le faire avec, ? : est le moyen le moins perturbateur d'exprimer cela et de continuer votre "flux"de codage.

46
répondu Tony Delroy 2015-07-30 05:24:39

Eh bien...

J'ai fait quelques tests avec GCC et cet appel de fonction:

add(argc, (argc > 1)?(argv[1][0] > 5)?50:10:1, (argc > 2)?(argv[2][0] > 5)?50:10:1, (argc > 3)?(argv[3][0] > 5)?50:10:1);

Le code assembleur résultant avec gcc-O3 avait 35 instructions.

Le code équivalent avec if / else + variables intermédiaires avait 36. Avec imbriqué if / else en utilisant le fait que 3 > 2 > 1, j'ai eu 44. Je n'ai même pas essayé d'étendre cela dans des appels de fonction séparés.

Maintenant, je n'ai fait aucune analyse de performance, ni fait un contrôle de qualité du code assembleur résultant, mais à quelque chose simple comme ça sans boucles E. T. C. je crois que plus court est mieux.

Il semble qu'il y ait une certaine valeur pour les opérateurs ternaires après tout: -)

C'est seulement si la vitesse du code est absolument cruciale, bien sûr. Les instructions If / else sont beaucoup plus faciles à lire lorsqu'elles sont imbriquées que quelque chose comme (c1)?(c2)?(c3)?(c4)?:1:2:3:4. Et avoir des expressions énormes comme arguments de fonction est Pas amusant.

Gardez également à l'esprit que les expressions ternaires imbriquées font refactorer le code-ou débogage en plaçant un tas de printfs () pratique à une condition-beaucoup plus difficile.

8
répondu thkala 2010-11-16 09:21:17

Le seul avantage potentiel pour les opérateurs ternaires par rapport aux instructions if simples à mon avis est leur capacité à être utilisée pour les initialisations, ce qui est particulièrement utile pour const:

Par exemple

const int foo = (a > b ? b : a - 10);

Faire cela avec un bloc if/else est impossible sans utiliser également une fonction cal. Si vous avez beaucoup de cas de const comme celui-ci, vous pourriez trouver qu'il y a un petit gain à initialiser un const correctement sur l'affectation avec if/else. Mesurer! Ne sera probablement même pas être mesurable cependant. La raison pour laquelle j'ai tendance à le faire est parce qu'en le marquant const le compilateur sait quand je fais quelque chose plus tard qui pourrait/changerait accidentellement quelque chose que je pensais avoir été corrigé.

Effectivement, ce que je dis, c'est que l'opérateur ternaire est important pour const-correctif, et const correctif est une bonne habitude d'être dans:

  1. cela vous permet d'économiser beaucoup de temps en laissant le compilateur vous aider à repérer les erreurs que vous faites
  2. cela peut potentiellement laisser le compilateur appliquer d'autres optimisations
7
répondu Flexo 2010-11-16 08:49:53

Vous supposez qu'il doit y avoir une distinction entre les deux quand, en fait, il y a un certain nombre de langues qui renoncent à l'instruction" if-else "en faveur d'une expression" if-else " (dans ce cas, elles peuvent même ne pas avoir l'opérateur ternaire, ce qui n'est plus nécessaire)

Imaginez:

x = if (t) a else b

Quoi Qu'il en soit, l'opérateur ternaire est une expression dans certains langages (C,C#,C++,Java,etc) qui Ne pas ont des expressions " if-else "et donc sert une expression distincte rôle là.

3
répondu 2010-11-16 09:17:22

Si vous êtes inquiet à ce sujet du point de vue de la performance, alors je serais très surpris s'il y avait une différence entre les deux.

Du point de vue du look 'n feel, il s'agit principalement de préférences personnelles. Si la condition est courte et que les parties true/false sont courtes, un opérateur ternaire est correct, mais tout ce qui est plus long tend à être meilleur dans une instruction if/else (à mon avis).

1
répondu Sean 2010-11-16 08:27:59