web-dev-qa-db-fra.com

si déclaration - évaluation de circuit court vs lisibilité

Parfois, une instruction if peut être assez longue ou longue, aussi, pour des raisons de lisibilité, il est préférable d’extraire les appels compliqués avant le if.

par exemple. cette:

if (SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall())
{
    // do stuff
}

dans cette

bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();

if (b1 || b2)
{
    //do stuff
}

(l'exemple fourni n'est pas cela mauvais, c'est juste pour illustration ... imaginez d'autres appels avec plusieurs arguments, etc.)

Mais avec cette extraction, j'ai perdu l'évaluation du court-circuit (SCE).

  1. Est-ce que je perds vraiment SCE à chaque fois? Existe-t-il un scénario dans lequel le compilateur est autorisé à "l'optimiser" tout en fournissant la SCE?
  2. Existe-t-il des moyens de conserver la lisibilité améliorée du deuxième extrait sans perdre de SCE?
90
relaxxx

Une solution naturelle ressemblerait à ceci:

bool b1 = SomeCondition();
bool b2 = b1 || SomeOtherCondition();
bool b3 = b2 || SomeThirdCondition();
// any other condition
bool bn = bn_1 || SomeFinalCondition();

if (bn)
{
  // do stuff
}

Cela présente l'avantage d'être facile à comprendre, applicable à tous les cas et d'avoir un comportement de court-circuit.


C'était ma solution initiale: Un bon modèle dans les appels de méthode et les corps de boucles for est le suivant:

if (!SomeComplicatedFunctionCall())
   return; // or continue

if (!SomeOtherComplicatedFunctionCall())
   return; // or continue

// do stuff

L’évaluation des courts-circuits offre les mêmes avantages en termes de performances de Nice, mais le code semble plus lisible.

118
Horia Coman

J'ai tendance à décomposer les conditions sur plusieurs lignes, à savoir:

if( SomeComplicatedFunctionCall()
 || OtherComplicatedFunctionCall()
  ) {

Même si vous avez plusieurs opérateurs (&&), il vous suffit d’avancer l’indention avec chaque paire de crochets. SCE entre toujours en jeu - inutile d'utiliser des variables. Écrire du code de cette façon le rendait beaucoup plus lisible depuis des années déjà. Exemple plus complexe:

if( one()
 ||( two()> 1337
  &&( three()== 'foo'
   || four()
    )
   )
 || five()!= 3.1415
  ) {
31
AmigoJack

Si vous avez de longues chaînes de conditions et que vous devez conserver une partie du court-circuit, vous pouvez utiliser des variables temporaires pour combiner plusieurs conditions. En prenant votre exemple, il serait possible de faire par exemple.

bool b = SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
if (b && some_other_expression) { ... }

Si vous avez un compilateur compatible C++ 11, vous pouvez utiliser expressions lambda pour combiner des expressions en fonctions, comme ci-dessus:

auto e = []()
{
    return SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
};

if (e() && some_other_expression) { ... }
28

Vous pouvez aussi utiliser:

bool b = someComplicatedStuff();
b = b || otherComplicatedStuff(); // it has to be: b = b || ...;  b |= ...; is bitwise OR and SCE is not working then 

et SCE travaillera.

Mais ce n'est pas beaucoup plus lisible que par exemple:

if (
    someComplicatedStuff()
    ||
    otherComplicatedStuff()
   )
21
KIIV

1) Oui, vous n'avez plus de SCE. Sinon, vous auriez ça

bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();

fonctionne dans un sens ou dans l’autre selon qu’il existe une instruction if ultérieurement. Bien trop complexe.

2) Ceci est basé sur l'opinion, mais pour des expressions raisonnablement complexes, vous pouvez faire:

if (SomeComplicatedFunctionCall()
    || OtherComplicatedFunctionCall()) {

Si cela s'avère trop complexe, la solution évidente consiste à créer une fonction qui évalue l'expression et l'appelle.

21
SJuan76

1) Est-ce que je perds vraiment SCE à chaque fois? Le compilateur est-il autorisé dans certains scénarios à "l'optimiser" tout en fournissant la SCE?

Je ne pense pas qu'une telle optimisation est autorisée; en particulier OtherComplicatedFunctionCall() peut avoir des effets secondaires.

2) Quelle est la meilleure pratique dans une telle situation? Est-ce seulement une possibilité (quand je veux SCE) d'avoir tout ce dont j'ai besoin directement à l'intérieur si et "juste le formater pour qu'il soit aussi lisible que possible"?

Je préfère le reformuler dans une fonction ou une variable avec un nom descriptif; qui préservera à la fois l’évaluation des courts-circuits et la lisibilité:

bool getSomeResult() {
    return SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
}

...

if (getSomeResult())
{
    //do stuff
}

Et à mesure que nous implémentons getSomeResult() sur la base de SomeComplicatedFunctionCall() et OtherComplicatedFunctionCall(), nous pourrions les décomposer de manière récursive si elles sont toujours compliquées.

14
songyuanyao

1) Est-ce que je perds vraiment SCE à chaque fois? Le compilateur est-il autorisé dans certains scénarios à "l'optimiser" tout en fournissant la SCE?

Non vous ne le faites pas, mais c'est appliqué différemment:

if (SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall())
{
    // do stuff
}

Ici, le compilateur ne lancera même pas OtherComplicatedFunctionCall() si SomeComplicatedFunctionCall() renvoie true.

bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();

if (b1 || b2)
{
    //do stuff
}

Ici, les deux fonctions will s'exécutent car elles doivent être stockées dans b1 Et b2. Ff b1 == true Alors b2 Ne sera pas évalué (SCE). Mais OtherComplicatedFunctionCall() a déjà été exécuté.

Si b2 N'est utilisé nulle part ailleurs, le compilateur pourrait être assez intelligent pour insérer l'appel de fonction dans le if si la fonction n'a aucun effet secondaire observable.

2) Quelle est la meilleure pratique dans une telle situation? Est-ce seulement une possibilité (quand je veux SCE) d'avoir tout ce dont j'ai besoin à l'intérieur si et "juste le formater pour qu'il soit aussi lisible que possible"?

Ça dépend. Avez-vous besoinOtherComplicatedFunctionCall() à exécuter en raison d'effets secondaires ou si l'impact sur les performances de la fonction est minime, vous devez utiliser la deuxième approche pour plus de lisibilité. Sinon, respectez la SCE lors de la première approche.

9
Sombrero Chicken

Une autre possibilité qui court-circuite et a les conditions dans un endroit:

bool (* conditions [])()= {&a, &b, ...}; // list of conditions
bool conditionsHold = true;
for(int i= 0; i < sizeOf(conditions); i ++){
     if (!conditions[i]()){;
         conditionsHold = false;
         break;
     }
}
//conditionsHold is true if all conditions were met, otherwise false

Vous pouvez insérer la boucle dans une fonction et la laisser accepter une liste de conditions et générer une valeur booléenne.

8
levilime

Très étrange: vous parlez de lisibilité quand personne ne mentionne l'utilisation de comment dans le code:

if (somecomplicated_function() || // let me explain what this function does
    someother_function())         // this function does something else
...

En plus de cela, je fais toujours précéder mes fonctions de commentaires, sur la fonction elle-même, sur ses entrées et ses sorties, et parfois je mets un exemple, comme vous pouvez le voir ici:

/*---------------------------*/
/*! interpolates between values
* @param[in] X_axis : contains X-values
* @param[in] Y_axis : contains Y-values
* @param[in] value  : X-value, input to the interpolation process
* @return[out]      : the interpolated value
* @example          : interpolate([2,0],[3,2],2.4) -> 0.8
*/
int interpolate(std::vector<int>& X_axis, std::vector<int>& Y_axis, int value)

Bien entendu, la mise en forme à utiliser pour vos commentaires peut dépendre de votre environnement de développement (Visual studio, JavaDoc sous Eclipse, ...)

En ce qui concerne SCE, je suppose que vous entendez par là:

bool b1;
b1 = somecomplicated_function(); // let me explain what this function does
bool b2 = false;
if (!b1) {                       // SCE : if first function call is already true,
                                 // no need to spend resources executing second function.
  b2 = someother_function();     // this function does something else
}

if (b1 || b2) {
...
}
4
Dominique