Qual é o objetivo do g ++ -Wreorder?

150

A opção g ++ -Wall inclui -Wreorder. O que essa opção faz é descrito abaixo. Não é óbvio para mim por que alguém se importaria (especialmente o suficiente para ativar isso por padrão em -Wall).

-Pedido (apenas C ++)
  Avisar quando a ordem dos inicializadores de membros fornecida no código não
  coincidir com a ordem em que eles devem ser executados. Por exemplo:

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

  O compilador irá reorganizar os inicializadores de membros para iej para
  coincidir com a ordem de declaração dos membros, emitindo um aviso àquele
  efeito. Este aviso é ativado por -Wall.
Peeter Joot
fonte
2
Algumas boas respostas aqui, mas um breve aparte no caso, é do interesse de ninguém: g ++ tem uma bandeira para tratar isso como um erro full-blown:-Werror=reorder
Max Barraclough

Respostas:

257

Considerar:

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

Agora ié inicializado com algum valor desconhecido, não zero.

Como alternativa, a inicialização de ipode ter alguns efeitos colaterais para os quais o pedido é importante. Por exemplo

A(int n) : j(n++), i(n++) { }
int3
fonte
80
Este deve realmente ser o exemplo na documentação.
Ben S
3
obrigado. Com a maioria dos nossos tipos sendo tipos POD com inicializadores simples, isso não me ocorreu. Seu exemplo é muito melhor que o exemplo manual do g ++.
Peeter Joot 01/12/2009
5
@ Mike, isso ocorre porque o seu compilador (gcc) inicializa variáveis ​​não inicializadas para 0, mas isso não é algo em que você deve confiar; i sendo 0 é apenas um efeito colateral do valor desconhecido para variáveis não inicializadas é 0.
ethanwu10
2
@Yakk A ordem era man page-> SO resposta. Aqui está um arquivo da página de manual de 2007 que lista este exemplo explicitamente. O comentário votado por Ben S é um exemplo hilário de alguém sugerindo que algo existe sem ao menos verificar se já existe. web.archive.org/web/20070712184121/http://linux.die.net/man/1/…
KymikoLoco
3
@KymikoLoco Isso é completamente errado. O exemplo na página do manual é o do OP (para onde ié inicializado 1). Aqui, ié inicializado como j, o que realmente demonstra um problema.
jazzpi
42

O problema é que alguém pode ver a lista de inicializadores de membros no construtor e pensar que eles são executados nessa ordem (j primeiro, depois i). Eles não são, são executados na ordem em que os membros são definidos na classe.

Suponha que você escreveu A(): j(0), i(j) {}. Alguém pode ler isso e pensar que eu acabo com o valor 0. Não, porque você o inicializou com j, que contém lixo eletrônico porque ele próprio não foi inicializado.

O aviso lembra que você escreva A(): i(j), j(0) {}, que parece muito mais suspeito.

Steve Jessop
fonte
Parece / cheira a peixe de fato! :) Definitivamente codifique o cheiro :) Obrigado por sua explicação clara, que é correta. :)
Será
1
"... lembra que você escreva A (): i (j), j (0) {} ..." Eu sugiro que você lembre de reordenar os alunos neste caso em particular.
2,718 22/02
18

Outras respostas forneceram alguns bons exemplos que justificam a opção de um aviso. Eu pensei em fornecer algum contexto histórico. O criador do C ++, Bjarne Stroustrup, explica em seu livro A linguagem de programação C ++ (3ª edição, página 259):

Os construtores dos membros são chamados antes que o corpo do construtor da própria classe seja executado. Os construtores são chamados na ordem em que são declarados na classe, e não na ordem em que aparecem na lista do inicializador. Para evitar confusão, é melhor especificar os inicializadores em ordem de declaração. Os destruidores de membros são chamados na ordem inversa da construção.

gkb0986
fonte
10

Isso pode te morder se seus inicializadores tiverem efeitos colaterais. Considerar:

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

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

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

O texto acima imprimirá "bar" e depois "foo", mesmo que intuitivamente alguém suponha que a ordem seja escrita na lista de inicializadores.

Como alternativa, se xe ysão de algum tipo definido pelo usuário com um construtor, esse construtor também pode ter efeitos colaterais, com o mesmo resultado não óbvio.

Também pode se manifestar quando o inicializador de um membro faz referência a outro membro.

Pavel Minaev
fonte
7

O aviso existe porque se você acabou de ler o construtor, parece que ele jestá sendo inicializado antes i. Isso se torna um problema se um é usado para inicializar o outro, como em

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

Quando você olha apenas para o construtor, isso parece seguro. Mas, na realidade, jainda não foi inicializado no ponto em que é usado para inicializar ie, portanto, o código não funcionará conforme o esperado. Daí o aviso.

jalf
fonte