Jaki jest sens g ++ -Wreorder?

150

Opcja g ++ -Wall obejmuje -Wreorder. Działanie tej opcji opisano poniżej. Nie jest dla mnie oczywiste, dlaczego kogoś to obchodzi (szczególnie na tyle, aby włączyć to domyślnie w -Wall).

-Wreorder (tylko C ++)
  Ostrzegaj, gdy kolejność inicjatorów składowych podana w kodzie nie
  pasują do kolejności, w jakiej muszą być wykonywane. Na przykład:

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

  Kompilator zmieni kolejność inicjatorów składowych dla i i j do
  dopasować kolejność deklaracji członków, wysyłając ostrzeżenie
  efekt. To ostrzeżenie jest włączane przez -Wall.
Peeter Joot
źródło
2
Kilka dobrych odpowiedzi, ale na marginesie, na wypadek, gdyby ktoś go zainteresował: g ++ ma flagę, która traktuje to jako pełny błąd:-Werror=reorder
Max Barraclough

Odpowiedzi:

257

Rozważać:

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

Teraz ijest inicjalizowany na nieznaną wartość, a nie na zero.

Alternatywnie inicjalizacja imoże mieć pewne skutki uboczne, dla których ważna jest kolejność. Na przykład

A(int n) : j(n++), i(n++) { }
int3
źródło
80
To naprawdę powinien być przykład w dokumentacji.
Ben S
3
dzięki. Ponieważ większość naszych typów to typy POD z prostymi inicjatorami, nie przyszło mi to do głowy. Twój przykład jest znacznie lepszy niż przykład ręczny g ++.
Peeter Joot
5
@Mike dzieje się tak dlatego, że Twój kompilator (gcc) inicjuje niezainicjowane zmienne na 0, ale nie jest to coś, na czym powinieneś polegać; i jest 0 to tylko efekt uboczny nieznanej wartości niezainicjowanych zmiennych wynosi 0.
ethanwu10
2
@Yakk Zlecenie to strona man-> tak odpowiedź. Oto archiwum strony podręcznika z 2007 roku, które wyraźnie wymienia ten przykład. Głosowany komentarz Bena S. to przezabawny przykład kogoś, kto sugeruje, że coś istnieje, nawet nie sprawdzając, czy już istnieje. web.archive.org/web/20070712184121/http://linux.die.net/man/1/…
KymikoLoco
3
@KymikoLoco To jest po prostu błędne. Przykład na stronie podręcznika to ten z OP (gdzie ijest zainicjowany 1). Tutaj ijest zainicjowany j, co w rzeczywistości pokazuje problem.
jazzpi
42

Problem polega na tym, że ktoś może zobaczyć listę inicjatorów składowych w konstruktorze i pomyśleć, że są one wykonywane w tej kolejności (najpierw j, potem i). Nie są, są wykonywane w kolejności, w jakiej elementy członkowskie są zdefiniowane w klasie.

Przypuśćmy, że napisałeś A(): j(0), i(j) {} . Ktoś mógłby to przeczytać i pomyśleć, że otrzymuję wartość 0. Tak nie jest, ponieważ zainicjowałeś go za pomocą j, który zawiera śmieci, ponieważ sam nie został zainicjalizowany.

Ostrzeżenie przypomina o konieczności pisania A(): i(j), j(0) {}, co, miejmy nadzieję, wygląda o wiele bardziej podejrzanie.

Steve Jessop
źródło
Wygląda / pachnie naprawdę podejrzanie! :) Zdecydowanie zapach kodu :) Dziękuję za jasne wyjaśnienie, które jest trafne. :)
Będzie
1
„… przypomina o napisaniu A (): i (j), j (0) {}…” Proponuję, aby przypomniał ci o zmianie kolejności członków klasy w tym konkretnym przypadku.
2.718
18

Inne odpowiedzi dostarczyły dobrych przykładów, które uzasadniają opcję ostrzeżenia. Pomyślałem, że przedstawię kontekst historyczny. Twórca C ++, Bjarne Stroustrup, wyjaśnia w swojej książce Język programowania C ++ (wydanie trzecie, strona 259):

Konstruktory elementów członkowskich są wywoływane przed wykonaniem treści własnego konstruktora klasy zawierającej. Konstruktory są wywoływane w kolejności, w jakiej są zadeklarowane w klasie, a nie w kolejności, w jakiej pojawiają się na liście inicjatorów. Aby uniknąć nieporozumień, najlepiej jest określić inicjatory w kolejności deklaracji. Destruktory składowe są wywoływane w odwrotnej kolejności.

gkb0986
źródło
10

Może to Cię ugryźć, jeśli inicjatory mają skutki uboczne. Rozważać:

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

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

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

Powyższe wypisze „bar”, a następnie „foo”, chociaż intuicyjnie można by założyć, że kolejność jest taka, jak zapisano na liście inicjalizacyjnej.

Alternatywnie, jeśli xiy są typu zdefiniowanego przez użytkownika z konstruktorem, ten konstruktor może również mieć skutki uboczne z tym samym nieoczywistym wynikiem.

Może się również objawiać, gdy inicjator dla jednego elementu członkowskiego odwołuje się do innego elementu członkowskiego.

Pavel Minaev
źródło
7

Ostrzeżenie istnieje, ponieważ jeśli po prostu przeczytasz konstruktora, wygląda na to, że jzostał zainicjowany wcześniej i. Staje się to problemem, jeśli jeden jest używany do inicjalizacji drugiego, jak w

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

Kiedy tylko spojrzysz na konstruktora, wygląda to bezpiecznie. Ale w rzeczywistości jnie został jeszcze zainicjowany w momencie, w którym jest używany do inicjalizacji i, więc kod nie będzie działał zgodnie z oczekiwaniami. Stąd ostrzeżenie.

jalf
źródło