2016-03-10 13 views
17

Il seguente semplice pezzo di codice venga compilato, anche se non capisco perché:C++ Forward Dichiarare classi nelle classi

class C { 
    class B; 

    class A { 
     B getB() { return B(); } 
    }; 

    class B { 
    }; 
}; 

int main(int, char**) 
{ 
    return 0; 
} 

Se poi commentare la "class C" roba, in modo che la dichiarazione anticipata di B , la definizione di A e la definizione di B sono più annidati all'interno di una classe, il codice non viene compilato, poiché B è di un tipo incompleto:

main.cpp: In member function 'B A::getB()': 
main.cpp:6: error: return type 'struct B' is incomplete 
main.cpp:6: error: invalid use of incomplete type 'struct B' 
main.cpp:3: error: forward declaration of 'struct B' 

I underst e cosa significa per un tipo essere incompleto, vale a dire che non è stato ancora definito e quindi il compilatore non può forse sapere quanto spazio allocare per esso. Ma perché B non è considerato incompleto nel codice sopra, dove A e B sono entrambi dichiarati e definiti all'interno di C?

risposta

10

Credo che questo sia una conseguenza della [basic.scope.class]:

Il potenziale portata di un nome dichiarato in una classe consiste non solo della regione dichiarativa seguente punto di dichiarazione del nome , ma anche di tutti i corpi di funzione , argomenti predefiniti, specifiche di eccezione e iniziatori start-up o equivalenti di membri di dati non statici in tale classe (compresi tali elementi nelle classi nidificate ).

Cioè, la portata della dichiarazione piena del B comprende il corpo della funzione membro della classe nidificata:

class C { 
    class B; // (1) 

    class A { 
     B getB() { 
      return B(); // both (1) and (2) in scope here 
         // since (2) is the complete type declaration, 
         // this is perfectly fine 
     } 
    }; 

    class B { // (2) 
    }; 
}; 

In confronto, se C erano un namespace invece di una classe , l'ambito della dichiarazione completa della classe B non si estenderebbe allo A::getB(). L'unica dichiarazione visibile sarebbe la forward-declaration di B che ho etichettato come (1) - quindi B() sarebbe la costruzione di un tipo incompleto lì.

+0

Fantastico, grazie per averlo chiarito. Ciò che ha sollevato questa particolare domanda è la classe ['result'] (http://pqxx.org/devprojects/libpqxx/doc/stable/html/Reference/) di libpqxx, che ha nidificato classi' tuple' e 'field', in quale 'tupla' costruisce un' campo' in uno dei suoi corpi funzione (ovvero, riga 183) – villapx

5

Lo standard prevede esplicitamente che il corpo del metodo venga interpretato dopo la classe che lo racchiude.

Pertanto, al momento della valutazione del corpo di C::A::getB(), A, B e C sono tutti i tipi completi.

+0

Qualche possibilità si potrebbe sapere dove potrei trovare quella verbosità? – villapx

+1

cercando di trovarlo ... sfortunatamente, il termine "funzione membro" è menzionato 582 volte nel progetto di standard PDF 2015 ... http://open-std.org/JTC1/SC22/WG21/docs/papers/2015 /n4527.pdf –

9

Il corpo di una funzione membro inline non viene elaborato finché la definizione della classe non è stata completamente elaborata.

Quindi, è possibile utilizzare:

class A 
{ 
    class B; 
    B getB() { return B(); } 

    class B {}; 
}; 

che permette anche variabili membro che non sono ancora dichiarati per essere utilizzato in linea definizione di funzione membro.

class Foo 
{ 
    int getBar() { return bar; } 

    int bar; 
}; 

Sono indovinare la stessa logica è esteso all'inline definizioni delle funzioni membro delle classi nidificate - cioè esse non vengono elaborati fino alla definizione della classe di contenimento è completamente elaborata.

PS Non riesco a individuare rapidamente il riferimento nello standard che verificherebbe il mio reclamo.

PS 2The answer by Barry ha il riferimento nello standard che rende valido il codice nella domanda.

+0

Mi hai battuto per la risposta. Stavo cercando di trovare le note. Penso che sia convertito da * Analogamente durante la ricerca del nome, quando un id non qualificato (5.1) utilizzato nella definizione di una funzione membro per la classe X risolve un membro statico, un enumeratore o un tipo nidificato di classe X o di un classe base di X, l'id non qualificato viene trasformato in un id qualificato (5.1) in cui lo identificatore nome-nidificato nomina la classe della funzione membro. * Da ** [class.mfct.non-static] **. Se sei d'accordo, vai avanti e usalo. – NathanOliver

+0

@NathanOliver Non penso che sia giusto. Questo dice solo che 'B' sarà trovato - ma' B' si troverà a prescindere dalla forward-declaration. Non so dove sia la sezione giusta, non riesco a trovarla neanche io. – Barry

+0

Forse * Se la classe X è definita in un ambito namespace, una classe nidificata Y può essere dichiarata in classe X e successivamente definita nella definizione di classe X o essere successivamente definita in un ambito di nomi che racchiude la definizione di classe X. * da ** [class.nest] ** – NathanOliver

0

Oltre a questo, quando ho la necessità di trasmettere dichiarare classi nidificate tendo a sentire l'odore un po 'di cattiva progettazione nel mio codice, il trucco che uso è:

// Foo.h 
class Foo { 
    class Bar { 
    }; 
}; 
class Foobar : public Foo::Bar {}; 


// Zoo.h 
/* Fwd declare */ 
class FooBar; 
+0

Puoi modificare la tua risposta – villapx

Problemi correlati