¿Cuál es el punto de g ++ -Wreorder?

150

La opción g ++ -Wall incluye -Wreorder. Lo que hace esta opción se describe a continuación. No es obvio para mí por qué a alguien le importaría (especialmente lo suficiente como para activar esto por defecto en -Wall).

-Wreorder (solo C ++)
  Avisar cuando el orden de los inicializadores de miembros dados en el código no
  coincidir con el orden en que deben ejecutarse. Por ejemplo:

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

  El compilador reorganizará los inicializadores de miembros para i y j para
  hacer coincidir el orden de declaración de los miembros, emitiendo una advertencia a ese
  efecto. Esta advertencia está habilitada por -Wall.
Peeter Joot
fuente
2
Algunas buenas respuestas aquí, pero un breve aparte en caso de que sea de interés para alguien: g ++ tiene una bandera para tratar esto como un error completo:-Werror=reorder
Max Barraclough

Respuestas:

257

Considerar:

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

Ahora ise inicializa a un valor desconocido, no a cero.

Alternativamente, la inicialización de ipuede tener algunos efectos secundarios para los cuales el orden es importante. P.ej

A(int n) : j(n++), i(n++) { }
int3
fuente
80
Este realmente debería ser el ejemplo en la documentación.
Ben S
3
Gracias. Como la mayoría de nuestros tipos son tipos POD con inicializadores simples, esto no se me ocurrió. Su ejemplo es mucho mejor que el ejemplo manual de g ++.
Peeter Joot el
55
@Mike esto se debe a que su compilador (gcc) inicializa las variables no inicializadas a 0, pero esto no es algo de lo que deba depender; ser 0 es solo un efecto secundario del valor desconocido para las variables no inicializadas es 0.
ethanwu10
2
@Yakk El orden era la página man-> TAN respuesta. Aquí hay un archivo de la página del manual de 2007 que enumera este ejemplo explícitamente. El comentario votado de Ben S es un ejemplo hilarante de alguien que sugiere que algo existe sin siquiera comprobar que ya existe. web.archive.org/web/20070712184121/http://linux.die.net/man/1/…
KymikoLoco
3
@KymikoLoco Eso es simplemente incorrecto. El ejemplo en la página del manual es el del OP (donde ise inicializa 1). Aquí, ise inicializa en j, lo que realmente demuestra un problema.
jazzpi
42

El problema es que alguien puede ver la lista de inicializadores miembros en el constructor y pensar que se ejecutan en ese orden (j primero, luego i). No lo son, se ejecutan en el orden en que los miembros se definen en la clase.

Supongamos que escribiste A(): j(0), i(j) {}. Alguien podría leer eso y pensar que i termina con el valor 0. No lo hace, porque lo inicializó con j, que contiene basura porque no se ha inicializado.

La advertencia le recuerda que debe escribir A(): i(j), j(0) {}, que con suerte se ve mucho más sospechoso.

Steve Jessop
fuente
¡Parece / huele a pescado! :) Definitivamente código de olor :) Gracias por su explicación clara que va directo al grano. :)
Será el
1
"... le recuerda que escriba A (): i (j), j (0) {} ..." Sugiero que le recuerde reordenar a los miembros de la clase en este caso particular.
2.718
18

Otras respuestas han proporcionado algunos buenos ejemplos que justifican la opción de una advertencia. Pensé en proporcionar un contexto histórico. El creador de C ++, Bjarne Stroustrup, explica en su libro El lenguaje de programación C ++ (3ra edición, Página 259):

Se llama a los constructores de los miembros antes de ejecutar el cuerpo del propio constructor de la clase que lo contiene. Los constructores se llaman en el orden en que se declaran en la clase en lugar del orden en que aparecen en la lista de inicializadores. Para evitar confusiones, es mejor especificar los inicializadores en orden de declaración. Los destructores miembros se llaman en el orden inverso de construcción.

gkb0986
fuente
10

Esto puede morderte si tus inicializadores tienen efectos secundarios. Considerar:

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

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

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

Lo anterior imprimirá "bar" y luego "foo", aunque intuitivamente uno supondría que el orden es como está escrito en la lista de inicializadores.

Alternativamente, si xy yson de algún tipo definido por el usuario con un constructor, ese constructor también puede tener efectos secundarios, con el mismo resultado no obvio.

También puede manifestarse cuando el inicializador de un miembro hace referencia a otro miembro.

Pavel Minaev
fuente
7

La advertencia existe porque si acaba de leer el constructor, parece que jse está inicializando antes i. Esto se convierte en un problema si uno se usa para inicializar al otro, como en

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

Cuando solo miras al constructor, esto parece seguro. Pero en realidad, jaún no se ha inicializado en el punto donde se usa para inicializar i, por lo que el código no funcionará como se esperaba. De ahí la advertencia.

jalf
fuente