web-dev-qa-db-fra.com

Affectation de variable dans la condition "si"

J'ai récemment perdu un peu de temps à trouver un bug dans mon code qui était causé par une faute de frappe:

if(a=b)

au lieu de:

if(a==b)

Je me demandais s'il y avait un cas particulier dans lequel vous voudriez affecter une valeur à une variable dans une instruction if, ou sinon, pourquoi le compilateur ne lance-t-il pas un avertissement ou une erreur?

28
Smash
if (Derived* derived = dynamic_cast<Derived*>(base)) {
   // do stuff with `derived`
}

Bien que cela soit souvent cité comme un anti-motif ("use virtual dispatch!"), Le type Derived a parfois une fonctionnalité que la Base ne possède tout simplement pas (et, par conséquent, des fonctions distinctes), différence sémantique.

34

Voici un historique de la syntaxe en question.

En C classique, la gestion des erreurs était fréquemment effectuée en écrivant quelque chose comme:

int error;
...
if(error = foo()) {
    printf("An error occured: %s\nBailing out.\n", strerror(error));
    abort();
}

Ou, chaque fois qu'il y avait un appel de fonction pouvant renvoyer un pointeur null, l'idiome était utilisé dans l'autre sens:

Bar* myBar;
... //in old C variables had to be declared at the start of the scope
if(myBar = getBar()) {
    //do something with myBar
}

Cependant, cette syntaxe est dangereusement proche de

if(myValue == bar()) ...

c’est pourquoi beaucoup de gens considèrent l’attribution dans une condition de style incorrect et les compilateurs ont commencé à en avertir (au moins avec -Wall). Toutefois, cet avertissement peut être évité en ajoutant un jeu supplémentaire de parenthèses:

if((myBar = getBar())) {  //tells the compiler: Yes, I really want to do that assignment!

Puis C99 est arrivé et vous a permis de mélanger des définitions et des déclarations. Ainsi, de nombreux développeurs écrivent souvent des choses comme:

Bar* myBar = getBar();
if(myBar) {

qui se sent maladroit. C'est pourquoi la nouvelle norme autorise les définitions à l'intérieur des conditions, pour fournir un moyen court et élégant de procéder ainsi:

if(Bar* myBar = getBar()) {

Il n'y a plus de danger dans cette instruction, vous donnez explicitement un type à la variable, souhaitant évidemment qu'elle soit initialisée. Cela évite également la ligne supplémentaire pour définir la variable, qui est Nice. Mais surtout, le compilateur peut maintenant facilement attraper ce type de bogue:

if(Bar* myBar = getBar()) {
    ...
}
foo(myBar->baz);  //compiler error
//or, for the C++ enthusiasts:
myBar->foo();     //compiler error

Sans la définition de variable à l'intérieur de l'instruction if, cette condition ne serait pas détectable.

En résumé, la question dans votre question est le produit de la simplicité et de la puissance du vieux C, mais elle est diabolique. Les compilateurs peuvent donc en avertir. Puisqu'il s'agit également d'un moyen très utile d'exprimer un problème commun, il existe désormais un moyen très concis et résistant aux bogues pour obtenir le même comportement. Et il y a beaucoup de bonnes utilisations possibles.

22
cmaster

Cela dépend si vous voulez écrire du code propre ou non. Au début du développement de C, l’importance du code propre N’était pas pleinement reconnue, et les compilateurs étaient très simplistes: Utiliser une affectation imbriquée comme celle-ci pouvait souvent produire un code plus rapide. Aujourd'hui, je ne vois pas de cas où un bon programmeur le ferait. Cela rend simplement le code moins lisible et plus difficile à maintenir. 

9
James Kanze

J'ai rencontré un cas où cela était utile tout récemment, alors j'ai pensé le poster.

Supposons que vous souhaitiez vérifier plusieurs conditions en une seule si, et si l'une de ces conditions est vraie, vous souhaitez générer un message d'erreur. Si vous souhaitez inclure dans votre message d'erreur la condition spécifique à l'origine de l'erreur, procédez comme suit:

std::string e;
if( myMap[e = "ab"].isNotValid() ||
    myMap[e = "cd"].isNotValid() ||
    myMap[e = "ef"].isNotValid() )
{
    // here, e has the key for which the validation failed
}

Donc, si la deuxième condition est celle qui a la valeur true, e sera égal à "cd". Cela est dû au comportement de court-circuit de || qui est imposé par la norme (sauf en cas de surcharge). Voir this answer pour plus de détails sur les courts-circuits.

4
elatalhm

pourquoi le compilateur ne lance pas un avertissement

Certains compilateurs will génèrent des avertissements pour les assignations suspectes dans une expression conditionnelle, bien que vous deviez généralement l'activer explicitement.

Par exemple, dans Visual C++, vous devez activer C4706 (ou les avertissements de niveau 4 en général). En général, j'active autant d'avertissements que possible et rend le code plus explicite afin d'éviter les faux positifs. Par exemple, si je voulais vraiment faire ceci:

if (x = Foo()) { ... }

Alors j'écrirais ça comme:

if ((x = Foo()) != 0) { ... }

Le compilateur voit le test explicite et suppose que l'affectation était intentionnelle. Par conséquent, vous ne recevez pas d'avertissement faux positif ici.

Le seul inconvénient de cette approche est que vous ne pouvez pas l'utiliser lorsque la variable est déclarée dans la condition. C'est-à-dire que vous ne pouvez pas récrire:

if (int x = Foo()) { ... }

comme

if ((int x = Foo()) != 0) { ... }

Syntaxiquement, ça ne marche pas. Vous devez donc soit désactiver l'avertissement, soit compromettre la portée de votre variable x.

1
Adrian McCarthy

Faire une assignation dans une if est une chose assez commune, bien que les gens le fassent aussi par accident. 

Le schéma habituel est:

if (int x = expensive_function_call())
{
  // ...do things with x
}

L'anti-pattern est l'endroit où vous assignez par erreur à des choses:

if (x = 1)
{
  // Always true
}
else
{
  // Never happens
}

Vous pouvez éviter cela en mettant vos constantes ou vos valeurs const en premier, afin que votre compilateur génère une erreur:

if (1 = x)
{
  // Compiler error, can't assign to 1
}

= vs == est quelque chose que vous devez développer un oeil pour. Je mets généralement des espaces autour de l'opérateur, il est donc plus évident de savoir quelle opération est en cours, car longname=longername ressemble beaucoup à longname==longername en un coup d'œil, mais = et == sont évidemment différents.

1
tadman