2012-08-31 14 views
43

Stavo scrivendo del codice oggi e ho ottenuto uno strano errore di compilazione, che sembra essere causato dall'inizializzazione delle variabili membro in un ordine diverso da quello in cui sono state dichiarate.Perché dovrei inizializzare le variabili membro nell'ordine in cui sono dichiarate?

Esempio:

class Test { 
    int a; 
    int b; 

public: 
    Test() : b(1), a(2) { 
    } 
}; 

int main() { 
    Test test; 
    return 0; 
} 

Poi se compilo con -Werror -Wall:

$ g++ -Werror -Wall test.cpp 
test.cpp: In constructor ‘Test::Test()’: 
test.cpp:3:9: error: ‘Test::b’ will be initialized after [-Werror=reorder] 
test.cpp:2:9: error: ‘int Test::a’ [-Werror=reorder] 
test.cpp:6:5: error: when initialized here [-Werror=reorder] 
cc1plus: all warnings being treated as errors 

mi rendo conto che -Wall chiede esplicitamente GCC andare over-the-top con avvisi, ma suppongo ci sia una ragione per tutti loro. Quindi, come potrebbe essere importante l'ordine di inizializzazione delle variabili dei membri?

+2

-Werror, come indicano i messaggi, indica al compilatore di considerare tutti gli avvisi come errori. Il codice come scritto è valido e il suo significato è ben definito (sebbene ci siano ragionevoli argomenti per non scriverlo in quel modo), ma con -Werror, il compilatore non è conforme allo standard perché rifiuta di compilare un codice valido. –

risposta

71

Il motivo è perché sono inizializzati nell'ordine in cui sono dichiarati nella classe, non nell'ordine in cui vengono inizializzati nel costruttore e si avverte che l'ordine del costruttore non verrà utilizzato.

Questo serve a prevenire errori in cui l'inizializzazione di b dipende da a o viceversa.

Il motivo di questo ordine è perché esiste un solo distruttore e deve scegliere un "ordine inverso" per distruggere il membro della classe. In questo caso, la soluzione più semplice era utilizzare l'ordine di dichiarazione all'interno della classe per assicurarsi che gli attributi fossero sempre distrutti nell'ordine inverso corretto.

27

Non dovresti perché diminuisce la leggibilità ed è potenzialmente fuorviante.

Se avete fatto:

Test() : b(1), a(b) {} 

sembrerebbe che b poi a sono stati entrambi impostati su 1, mentre in realtà il valore non inizializzato di b viene utilizzato per inizializzare a prima b è inizializzato a 1.

+2

+1 per l'esempio per mostrare perché l'ordine conta davvero e cosa può andare storto nel caso qualcuno assuma che l'ordine sia quello che hanno dichiarato nel costruttore. – atuljangra

12

In realtà il compilatore sempre inizializza le variabili nell'ordine di dichiarazione, anche se si scrivono gli inizializzatori in un ordine diverso. Pertanto se non si scrivono le inizializzazioni nell'ordine di dichiarazione, l'ordine dei propri inizializzatori non si adatta all'ordine di inizializzazione, il che potrebbe portare a bug sottili se le inizializzazioni dipendono l'una dall'altra.

Ad esempio, si consideri il codice

Test(): b(42), a(b) {} 

Questo è un errore, perché a viene inizializzato prima b, ma sembra OK. Se si scrive in ordine di dichiarazione (che è l'ordine di inizializzazione), il bug si ovvia:

Test(): a(b), b(42) {} 

Nota che il bug può essere anche più sottile di quello; per esempio immagini a e b sono tipi di classe che generano qualcosa nel loro costruttore; quindi con l'ordine "errato" penseresti che l'output di b debba apparire prima di a quando in realtà avverrà il contrario. Se l'output di a appare per primo porterà a un file non valido, che è anche un bug, ma non c'è modo che il compilatore possa notare il problema se i costruttori si trovano in un'altra unità di traduzione (a parte il fatto che il compilatore non può sapere se il riordino è o non è un bug). Pertanto è ragionevole che il compilatore avvisi solo su ogni istanza di ordine non corrispondente.

36

Perché dovrei inizializzare le variabili membro nell'ordine in cui sono dichiarate?

I membri saranno essere inizializzato nello stesso ordine in cui sono dichiarate, che tu lo voglia o no. L'avviso indica che l'ordine richiesto è diverso dall'ordine di esecuzione effettivo dell'inizializzazione.

+3

+1 per una succinta risposta al punto che non usa la parola "compilatore". –

+1

Bella risposta. Possiamo supporre che questo sia solo lo standard C++ facendo in modo che tutti i costruttori compilino in modo deterministico, o c'è qualche motivo tecnico/prestazionale che i membri dovrebbero essere sempre inizializzati nell'ordine in cui sono dichiarati? –

+4

@XavierHolt: nella lingua, per tutti gli oggetti allocati dinamicamente, l'ordine di costruzione e distruzione è invertito. Se l'ordine di inizializzazione era determinato dall'elenco di inizializzazione, l'ordine di inizializzazione in una classe con più costruttori non sarebbe stato definito e l'ordine di distruzione dei membri non sarebbe stato definito. Il motivo per cui l'ordine di costruzione/distruzione è invertito è quello di assicurare che se un oggetto dipende da un altro, il secondo non sarà distrutto prima del primo. –

5

Mi rendo conto che -Wall chiede esplicitamente a GCC di andare oltre gli avvisi, ma presumo che ci sia un motivo per tutti.

-Wall è solo un inizio. Contrariamente a ciò che implica il nome, -La parete non abilita tutti gli avvisi. Ci sono alcuni avvertimenti che sono probabilmente "sopra le righe", ma quelli sono esattamente gli avvertimenti che -La non consente. Uso sempre -Wall e altri.

Per quanto riguarda il vostro reclamo, come altri hanno già notato, vi è una buona ragione per questo avvertimento. Solo perché si specifica un ordine non significa che il compilatore utilizzerà quell'ordine. L'ordine che il compilatore deve utilizzare secondo lo standard si basa sulla definizione della classe.

Problemi correlati