Quel est l'intérêt de g ++ -Wreorder?

150

L'option g ++ -Wall inclut -Wreorder. Ce que fait cette option est décrit ci-dessous. Il n'est pas évident pour moi pourquoi quelqu'un s'en soucierait (surtout assez pour l'activer par défaut dans -Wall).

-Wreorder (C ++ uniquement)
  Avertir lorsque l'ordre des initialiseurs de membre donné dans le code ne fonctionne pas
  correspondent à l'ordre dans lequel ils doivent être exécutés. Par exemple:

    struct A {
      int i;
      int j;
      A (): j (0), i (1) {}
    };

  Le compilateur réorganisera les initialiseurs de membres pour i et j en
  correspond à l'ordre de déclaration des membres, en émettant un avertissement
  effet. Cet avertissement est activé par -Wall.
Peeter Joot
la source
2
Quelques bonnes réponses ici, mais un bref aparté au cas où cela intéresserait n'importe qui: g ++ a un drapeau pour traiter cela comme une erreur à part entière:-Werror=reorder
Max Barraclough

Réponses:

257

Considérer:

struct A {
    int i;
    int j;
    A() : j(0), i(j) { }
};

Maintenant iest initialisé à une valeur inconnue, pas zéro.

Alternativement, l'initialisation de ipeut avoir des effets secondaires pour lesquels l'ordre est important. Par exemple

A(int n) : j(n++), i(n++) { }
int3
la source
80
Cela devrait vraiment être l'exemple de la documentation.
Ben S
3
Merci. La plupart de nos types étant des types POD avec des initialiseurs simples, cela ne m'est pas venu à l'esprit. Votre exemple est bien meilleur que celui du manuel g ++.
Peeter Joot
5
@Mike c'est parce que votre compilateur (gcc) initialise les variables non initialisées à 0, mais ce n'est pas quelque chose dont vous devriez dépendre; i étant 0 est juste un effet secondaire de la valeur inconnue pour les variables non initialisées est 0.
ethanwu10
2
@Yakk L'ordre était la page de manuel-> SO réponse. Voici une archive de la page de manuel de 2007 qui répertorie explicitement cet exemple. Le commentaire positif de Ben S est un exemple hilarant de quelqu'un suggérant que quelque chose existe sans même vérifier que c'est déjà le cas. web.archive.org/web/20070712184121/http://linux.die.net/man/1/...
//linux.die.net/man/1
3
@KymikoLoco C'est tout simplement faux. L'exemple de la page de manuel est celui de l'OP (où iest initialisé à 1). Ici, iest initialisé à j, ce qui démontre en fait un problème.
jazzpi
42

Le problème est que quelqu'un peut voir la liste des initialiseurs de membres dans le constructeur et penser qu'ils sont exécutés dans cet ordre (j d'abord, puis i). Ils ne le sont pas, ils sont exécutés dans l'ordre dans lequel les membres sont définis dans la classe.

Supposons que vous ayez écrit A(): j(0), i(j) {}. Quelqu'un pourrait lire ça et penser que je finit par avoir la valeur 0. Ce n'est pas le cas, parce que vous l'avez initialisé avec j, qui contient des fichiers indésirables parce qu'il n'a pas lui-même été initialisé.

L'avertissement vous rappelle d'écrire A(): i(j), j(0) {}, ce qui, espérons-le, semble beaucoup plus louche.

Steve Jessop
la source
Ça a l'air / sent vraiment de poisson! :) Certainement une odeur de code :) Merci pour votre explication claire qui va droit au but. :)
Sera
1
"... vous rappelle d'écrire A (): i (j), j (0) {} ..." Je suggère que cela vous rappelle de réorganiser les membres de la classe dans ce cas particulier.
2.718
18

D'autres réponses ont fourni de bons exemples qui justifient l'option d'avertissement. J'ai pensé fournir un contexte historique. Le créateur du C ++, Bjarne Stroustrup, explique dans son livre Le langage de programmation C ++ (3e édition, page 259):

Les constructeurs des membres sont appelés avant que le corps du propre constructeur de la classe contenant ne soit exécuté. Les constructeurs sont appelés dans l'ordre dans lequel ils sont déclarés dans la classe plutôt que dans l'ordre dans lequel ils apparaissent dans la liste d'initialiseurs. Pour éviter toute confusion, il est préférable de spécifier les initialiseurs dans l'ordre des déclarations. Les destructeurs de membres sont appelés dans l'ordre inverse de la construction.

gkb0986
la source
10

Cela peut vous mordre si vos initialiseurs ont des effets secondaires. Considérer:

int foo() {
    puts("foo");
    return 1;
}

int bar() {
    puts("bar");
    return 2;
}

struct baz {
    int x, y;
    baz() : y(foo()), x(bar()) {}
};

Ce qui précède affichera "bar" puis "foo", même si intuitivement on supposerait que l'ordre est tel qu'écrit dans la liste d'initialisation.

Sinon, si xety sont d'un type défini par l'utilisateur avec un constructeur, ce constructeur peut également avoir des effets secondaires, avec le même résultat non évident.

Il peut également se manifester lorsque l'initialiseur d'un membre fait référence à un autre membre.

Pavel Minaev
la source
7

L'avertissement existe car si vous venez de lire le constructeur, il semble qu'il jsoit initialisé avant i. Cela devient un problème si l'un est utilisé pour initialiser l'autre, comme dans

struct A {
  int i;
  int j;
  A(): j (0), i (this->j) { }
};

Lorsque vous regardez simplement le constructeur, cela semble sûr. Mais en réalité, jn'a pas encore été initialisé au point où il est utilisé pour l'initialisation i, et donc le code ne fonctionnera pas comme prévu. D'où l'avertissement.

jalf
la source