Was ist der Sinn von g ++ -Wreorder?


150

Die Option g ++ -Wall enthält -Wreorder. Was diese Option bewirkt, wird unten beschrieben. Mir ist nicht klar, warum es jemanden interessieren würde (insbesondere genug, um dies standardmäßig in -Wall einzuschalten).

-Wororder (nur C ++)
  Warnen, wenn die im Code angegebene Reihenfolge der Mitgliedsinitialisierer nicht stimmt
  stimmen mit der Reihenfolge überein, in der sie ausgeführt werden müssen. Zum Beispiel:

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

  Der Compiler ordnet die Elementinitialisierer für i und j neu an
  Passen Sie die Deklarationsreihenfolge der Mitglieder an und senden Sie eine Warnung darauf
  bewirken. Diese Warnung wird von -Wall aktiviert.

2
Einige gute Antworten hier, aber eine kurze Seite, falls es für irgendjemanden von Interesse ist: g ++ hat eine Flagge, um dies als vollständigen Fehler zu behandeln:-Werror=reorder
Max Barraclough

Antworten:


257

Erwägen:

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

Jetzt iwird auf einen unbekannten Wert initialisiert, nicht auf Null.

Alternativ kann die Initialisierung von ieinige Nebenwirkungen haben, für die die Reihenfolge wichtig ist. Z.B

A(int n) : j(n++), i(n++) { }

80
Dies sollte wirklich das Beispiel in der Dokumentation sein.
Ben S

3
Vielen Dank. Da die meisten unserer Typen POD-Typen mit einfachen Initialisierern sind, ist mir dies nicht in den Sinn gekommen. Ihr Beispiel ist viel besser als das manuelle Beispiel für g ++.
Peeter Joot

5
@Mike Dies liegt daran, dass Ihr Compiler (gcc) nicht initialisierte Variablen auf 0 initialisiert, aber darauf sollten Sie sich nicht verlassen. i 0 zu sein ist nur ein Nebeneffekt des unbekannten Wertes für nicht initialisierte Variablen ist 0.
ethanwu10

2
@ Yakk Die Bestellung war Manpage-> SO Antwort. Hier ist ein Archiv der Manpage von 2007, in dem dieses Beispiel explizit aufgeführt ist. Der optimierte Kommentar von Ben S ist ein lustiges Beispiel für jemanden, der vorschlägt, dass etwas existiert, ohne zu überprüfen, ob es bereits existiert. web.archive.org/web/20070712184121/http://linux.die.net/man/1/…
KymikoLoco

3
@KymikoLoco Das ist einfach falsch. Das Beispiel in der Manpage ist das aus dem OP (wo ies initialisiert ist 1). Hier iwird auf initialisiert j, was tatsächlich ein Problem zeigt.
Jazzpi

42

Das Problem ist, dass jemand möglicherweise die Liste der Mitgliedsinitialisierer im Konstruktor sieht und denkt, dass sie in dieser Reihenfolge ausgeführt werden (j zuerst, dann i). Sie sind nicht, sie werden in der Reihenfolge ausgeführt, in der die Mitglieder in der Klasse definiert sind.

Angenommen, Sie haben geschrieben A(): j(0), i(j) {}. Jemand könnte das lesen und denken, dass ich am Ende den Wert 0 habe. Das tut es nicht, weil Sie es mit j initialisiert haben, das Junk enthält, weil es selbst nicht initialisiert wurde.

Die Warnung erinnert Sie an das Schreiben A(): i(j), j(0) {}, das hoffentlich viel fauler aussieht.


Sieht / riecht in der Tat fischig! :) Auf jeden Fall Code-Geruch :) Vielen Dank für Ihre klare Erklärung, die auf den Punkt kommt. :)
Will

1
"... erinnert Sie daran, A () zu schreiben: i (j), j (0) {} ..." Ich schlage vor, dass es Sie daran erinnert, die Klassenmitglieder in diesem speziellen Fall neu zu ordnen.
2.718

18

Andere Antworten haben einige gute Beispiele geliefert, die die Option für eine Warnung rechtfertigen. Ich dachte, ich würde einen historischen Kontext liefern. Der Schöpfer von C ++, Bjarne Stroustrup, erklärt in seinem Buch Die Programmiersprache C ++ (3. Auflage, Seite 259):

Die Konstruktoren der Mitglieder werden aufgerufen, bevor der Hauptkonstruktor der enthaltenden Klasse ausgeführt wird. Die Konstruktoren werden in der Reihenfolge aufgerufen, in der sie in der Klasse deklariert sind, und nicht in der Reihenfolge, in der sie in der Initialisierungsliste angezeigt werden. Um Verwirrung zu vermeiden, ist es am besten, die Initialisierer in der Reihenfolge der Deklaration anzugeben. Die Elementzerstörer werden in umgekehrter Reihenfolge der Konstruktion aufgerufen.


10

Dies kann Sie beißen, wenn Ihre Initialisierer Nebenwirkungen haben. Erwägen:

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

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

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

Das Obige gibt "bar" und dann "foo" aus, obwohl man intuitiv davon ausgehen würde, dass die Reihenfolge der in der Initialisierungsliste angegebenen entspricht.

Alternativ, wenn xundy mit einem Konstruktor ein benutzerdefinierter Typ vorliegt, kann dieser Konstruktor auch Nebenwirkungen haben, mit demselben nicht offensichtlichen Ergebnis.

Es kann sich auch manifestieren, wenn der Initialisierer für ein Mitglied auf ein anderes Mitglied verweist.


7

Die Warnung ist vorhanden, denn wenn Sie nur den Konstruktor lesen, sieht es so aus, jals würde er zuvor initialisiert i. Dies wird zu einem Problem, wenn einer zum Initialisieren des anderen verwendet wird, wie in

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

Wenn Sie sich nur den Konstruktor ansehen, sieht dies sicher aus. In Wirklichkeit wurde jes jedoch noch nicht an dem Punkt initialisiert, an dem es zum Initialisieren verwendet wird i, sodass der Code nicht wie erwartet funktioniert. Daher die Warnung.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.