2015-05-09 14 views
8

Si consideri il seguente codice:Questo comportamento non definito o un falso avviso positivo?

class A { 
private: 
    int a; 

public: 
    A(int a) : a(a) { } 
}; 

class B : public A { 
private: 
    int b; 

    bool init() { 
    b = 0; 
    return true; 
    } 

public: 
    // init() is a hack to initialize b before A() 
    // B() : b(0), A(b) {} yields -Wreorder 
    // B() : A((b = 0)) {} no warning (but this one doesn't work so well with non-pod (pointer) types) 
    B() : A(init() ? b : 0) {} 
}; 

Ora si cerca di compilare il codice con clangore ...

$ clang++ test.cpp -fsyntax-only 
test.cpp:19:20: warning: field 'b' is uninitialized when used here [-Wuninitialized] 
B() : A(init() ? b : 0) {} 
       ^
1 warning generated. 

GCC non stampa eventuali avvisi, nemmeno con -Wall -Wextra e -pedantic.

+0

Perché 'B :: init' restituisce un valore costante? Suggerisco di dichiararlo "void". Non ha senso avere una funzione che restituisce un valore costante che non è determinato dal contenuto all'interno della funzione. –

+0

@ThomasMatthews: È un esempio ridotto del mio vero codice, '(void) a' era solo un tentativo di silenziare' -Wunused-private-field'. – Thomas

+1

@ThomasMatthews - basato sulla natura astratta di questo esempio, è ragionevolmente sicuro assumere che 'B :: init' restituisce una costante fa parte della creazione di [MVCE] (http://stackoverflow.com/help/mcve). –

risposta

7

È un comportamento non definito. Secondo [class.base.init]:

In un costruttore non delega, procede inizializzazione nel seguente ordine:
- primo, e solo per il costruttore della classe più derivata (1.8), virtuale classi di base ...
- Quindi, le classi di base diretta sono inizializzate in ordine di dichiarazione poiché compaiono nell'elenco degli specificatori di base (indipendentemente dall'ordine degli inizializzatori di mem).
- Quindi, i membri di dati non statici sono inizializzati nell'ordine in cui sono stati dichiarati nella definizione di classe (di nuovo indipendentemente dall'ordine degli inizializzatori di mem).

b non è stato inizializzato al momento della inizializzazione della classe base A. L'assegnazione b = 0 è di per sé un comportamento non definito per lo stesso motivo - b non è stato ancora inizializzato quando viene chiamato. Il suo costruttore di default sarebbe ancora chiamato dopo il costruttore di A.

Se si vuole assicurare che b viene inizializzato prima, l'approccio tipico è il base-from-member idiom:

struct B_member { 
    int b; 
    B_member() : b(0) { } 
}; 

class B : public B_member, public A 
{ 
public: 
    B() : A(b) // B_member gets initialized first, which initializes b 
       // then A gets initialized using 'b'. No UB here. 
    { }; 
}; 
+1

Questo avrebbe senso come risposta per i membri non POD, ma considera che 'int main() {int a; a = 1; } 'è perfettamente valido anche se' a' non è mai stato inizializzato. Per i tipi POD, l'assegnazione può essere una valida alternativa all'inizializzazione. – hvd

+1

@hvd 'int a;' ancora default-inizializza 'a'. Ma sì, questo sarebbe sicuramente più un problema per i non-POD. – Barry

+0

@ L'inizializzazione predefinita di Barry non sta inizializzando gli scalari. L'oggetto è ritenuto non inizializzato, con valore indeterminato. – Columbo

6

In entrambi i casi, chiamando una funzione di membro prima di classi base vengono inizializzati invoca un comportamento indefinito. §12.6.2/16:

funzioni dell'utente (comprese funzioni membro virtuali, 10.3) può essere chiamato per un oggetto in costruzione. Analogamente, un oggetto sotto la costruzione può essere l'operando dell'operatore typeid (5.2.8) o di uno dynamic_cast (5.2.7). Tuttavia, se queste operazioni vengono eseguite in un ctor-inizializzatore (o in una funzione chiamata direttamente o indirettamente da un ctor-inizializzatore) prima di tutte le mem-initializers per classi di base hanno completato, il risultato l'operazione è indefinita. [Esempio:

class A { 
public: 
    A(int); 
}; 

class B : public A { 
    int j; 
public: 
    int f(); 
    B() : A(f()), // undefined: calls member function 
       // but base A not yet initialized 

    j(f()) { } // well-defined: bases are all initialized 
}; 

Tuttavia, l'accesso di assegnamento b sé soddisfacente, poiché ha inizializzazione vacuo e la durata inizia non appena memorizzazione viene acquisita per esso (che è accaduto molto tempo prima del costruttore chiamata iniziata).Quindi

class B : public A { 
private: 
    int b; 

public: 
    B() : A(b=0) {} 
}; 

è ben definito.

+0

Per quale tipo di accesso è ben definito? – Barry

+0

@Barry Questo non dipende dal tipo da solo, ma dall'inizializzazione in ogni caso particolare. §3.8 stabilisce che la durata di un oggetto inizia dopo che è stata allocata la memoria e che è stata eseguita l'inizializzazione non vacua - * se presente * (che non è il caso qui). Pertanto, la validità di un accesso dipende interamente dall'inizializzazione utilizzata in ciascun caso: se ad es. il tuo oggetto avrà il suo banale costruttore predefinito "chiamato", che assegna sth. ad esso prima che il costruttore sia "chiamato" va bene. – Columbo

+0

Quindi per i membri con costruttori di base banali, che in seguito non vengono visualizzati nel mem-inizializzatore? – Barry

Problemi correlati