2009-06-04 19 views
164

Recentemente ho avuto bloccato in una situazione come questa:dichiarazione anticipata di tipi nidificati/classi in C++

class A 
{ 
public: 
    typedef struct/class {...} B; 
... 
    C::D *someField; 
} 

class C 
{ 
public: 
    typedef struct/class {...} D; 
... 
    A::B *someField; 
} 

Di solito è possibile dichiarare un nome di classe:

class A; 

ma non è possibile in avanti dichiarare un tipo annidato, il seguente causa errore di compilazione.

class C::D; 

Qualche idea?

+5

Perché ne hai bisogno? Si noti che è possibile inoltrare la dichiarazione se si tratta di un membro della stessa classe da definire: classe X {classe Y; Y * a; }; classe X: Y {}; –

+1

Errore affascinante. –

+0

Questa soluzione ha funzionato per me (spazio dei nomi C {class D;};): http://stackoverflow.com/questions/22389784/c-code-fails-to-compile-after-upgrading-xcode-5-0-5 -1-forward-declaration –

risposta

179

Non puoi farlo, è un buco nel linguaggio C++. Dovrai annullare la nidificazione di almeno una delle classi nidificate.

+5

Grazie per la risposta. Nel mio caso, non sono le mie classi nidificate. Speravo di evitare un'enorme dipendenza dal file di intestazione della libreria con un piccolo riferimento in avanti. Mi chiedo se C++ 11 l'ha risolto? –

+46

Oh. Proprio quello che non volevo che Google si presentasse. Grazie comunque per la risposta concisa. – learnvst

+15

Lo stesso qui ... qualcuno sa * perché * non è possibile? Sembra che ci siano casi d'uso validi e questa mancanza impedisce la coerenza dell'architettura in alcune situazioni. –

0

Non definirei questa risposta, ma comunque una scoperta interessante: Se si ripete la dichiarazione della propria struttura in uno spazio dei nomi chiamato C, tutto è a posto (in gcc almeno). Quando viene trovata la definizione della classe di C, sembra per sovrascrivere in silenzio il namspace C.

namespace C { 
    typedef struct {} D; 
} 

class A 
{ 
public: 
typedef struct/class {...} B; 
... 
C::D *someField; 
} 

class C 
{ 
public: 
    typedef struct/class {...} D; 
... 
    A::B *someField; 
} 
+1

Ho provato questo con cygwin gcc e non si compila se provi a fare riferimento a A.someField. C :: D nella classe A definisce in realtà la struct (vuota) nel namespace, non la struct nella classe C (BTW non compilato in MSVC) – Dolphin

+0

Si dà l'errore: "'classe C 'redeclared come diverso tipo di simbolo " – Calmarius

+7

Sembra un bug GCC. Sembra pensare che un nome di spazio dei nomi possa nascondere un nome di classe nello stesso ambito. –

25
class IDontControl 
{ 
    class Nested 
    { 
     Nested(int i); 
    }; 
}; 

avevo bisogno di un riferimento in avanti come:

class IDontControl::Nested; // But this doesn't work. 

mia soluzione era:

class IDontControl_Nested; // Forward reference to distinct name. 

In seguito, quando ho potuto utilizzare la definizione completa:

Questa tecnica sarebbe probabilmente più problematica di quanto valga se esistessero costruttori complicati o altre funzioni membro speciali che non sono state ereditate senza problemi. Potrei immaginare che alcuni modelli di magia reagiscano male.

Ma nel mio caso molto semplice, sembra funzionare.

+10

In C++ 11 è possibile ereditare i costruttori usando 'basename :: basename;' nella classe derivata, quindi nessun problema con i complicati cori. – Xeo

+1

Bel trucco, ma non funzionerà se puntatore a IDontControl :: Nested utilizzato all'interno della stessa intestazione (dove inoltrato dichiarato) e accessibile da codice esterno che include anche la definizione completa di IDontControl. (Perché il compilatore non corrisponderà a IDontControl_Nested e IDontControl :: Nested). La soluzione alternativa è eseguire il cast statico. –

3

Se si vuole davvero evitare #including il file di intestazione brutto nel file di intestazione, si potrebbe fare questo:

file di HPP:

class MyClass 
{ 
public: 
    template<typename ThrowAway> 
    void doesStuff(); 
}; 

di file cpp

#include "MyClass.hpp" 
#include "Annoying-3rd-party.hpp" 

template<> void MyClass::doesStuff<This::Is::An::Embedded::Type>() 
{ 
    // ... 
} 

Ma poi:

  1. si dovrà specificare il tipo incorporato in fase di chiamata (soprattutto se la vostra funzione non accetta parametri di tipo embedded)
  2. la funzione non può essere virtuale (perché è un modello)

Quindi, sì, compromessi ...

+1

Che diamine è un file 'hpp'? – Neal

+6

lol, un file di intestazione **. Hpp ** viene utilizzato nei progetti C++ per distinguerlo da un file di intestazione C che termina tipicamente con .h. Quando si lavora con C++ e C nello stesso progetto, alcune persone preferiscono .hpp e .cpp per i file C++, per renderlo esplicitamente con il tipo di file con cui hanno a che fare, e .h e .c per i file C. – bitek

0

Questa sarebbe una soluzione (almeno per il problema descritto nella domanda - non per il problema reale, vale a dire, quando non si ha il controllo sulla definizione di C:

class C_base { 
public: 
    class D { }; // definition of C::D 
    // can also just be forward declared, if it needs members of A or A::B 
}; 
class A { 
public: 
    class B { }; 
    C_base::D *someField; // need to call it C_base::D here 
}; 
class C : public C_base { // inherits C_base::D 
public: 
    // Danger: Do not redeclare class D here!! 
    // Depending on your compiler flags, you may not even get a warning 
    // class D { }; 
    A::B *someField; 
}; 

int main() { 
    A a; 
    C::D * test = a.someField; // here it can be called C::D 
} 
Problemi correlati