Dichiarazione diretta di tipi / classi nidificati in C ++


197

Di recente mi sono bloccato in una situazione come questa:

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

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

Di solito puoi dichiarare un nome di classe:

class A;

Ma non è possibile inoltrare un tipo nidificato, ciò che segue causa un errore di compilazione.

class C::D;

Qualche idea?


6
Perché ne hai bisogno? Nota che puoi inoltrare dichiarare se si sta definendo un membro della stessa classe: class X {class Y; Y * a; }; classe X :: Y {};
Johannes Schaub - litb

Questa soluzione ha funzionato per me (namespace C {classe D;};): stackoverflow.com/questions/22389784/...
Albert Wiersch

Ho trovato un collegamento per la
bitlixi

Risposte:


224

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


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

61
Oh. Proprio quello che non volevo mostrare a Google. Grazie comunque per la risposta concisa.
apprese l'

19
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.
Maël Nison,

Puoi usare amico. E inserisci un commento in cui lo stai usando per aggirare il buco in C ++.
Erik Aronesty,

3
Ogni volta che incontro tali difetti inutili in questo linguaggio irascibile, sono diviso tra il ridere e il pianto
SongWithWords

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

Avevo bisogno di un riferimento futuro come:

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

La mia soluzione alternativa era:

class IDontControl_Nested; // Forward reference to distinct name.

Più tardi, quando ho potuto usare la definizione completa:

#include <idontcontrol.h>

// I defined the forward ref like this:
class IDontControl_Nested : public IDontControl::Nested
{
    // Needed to make a forwarding constructor here
    IDontControl_Nested(int i) : Nested(i) { }
};

Questa tecnica sarebbe probabilmente più problematica di quanto valga la pena se ci fossero costruttori complicati o altre funzioni speciali dei membri che non sono state ereditate senza problemi. Potrei immaginare che certi modelli di magia reagiscano male.

Ma nel mio caso molto semplice, sembra funzionare.


16
In C ++ 11 puoi ereditare i costruttori using basename::basename;nella classe derivata, quindi nessun problema con i ctors complicati.
Xeo

1
Un bel trucco, ma non funzionerà se il puntatore a IDontControl :: Nested viene utilizzato nella stessa intestazione (dove viene dichiarato in avanti) e vi si accede da un 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 cast statici.
Artem Pisarenko,

Consiglierei di fare il contrario e di typedef
tenere

3

Se vuoi davvero evitare #includendo il brutto file di intestazione nel tuo file di intestazione, puoi farlo:

file hpp:

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

file cpp

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

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

Ma allora:

  1. dovrai specificare il tipo incorporato al momento della chiamata (specialmente se la tua funzione non accetta alcun parametro del tipo incorporato)
  2. la tua funzione non può essere virtuale (perché è un modello)

Quindi sì, compromessi ...


1
Che diamine è un hppfile?
Naftali aka Neal,

7
lol, un file di intestazione .hpp viene utilizzato nei progetti C ++ per distinguerlo da un file di intestazione C che in genere termina con .h. Quando lavorano con C ++ e C nello stesso progetto alcune persone preferiscono .hpp e .cpp per i file C ++, per renderlo esplicitamente con quale tipo di file hanno a che fare, e .h e .c per i file C.
Bitek,

2

Questo può essere fatto dichiarando in avanti la classe esterna come spazio dei nomi .

Esempio: dobbiamo usare una classe nidificata others :: A :: Nested in others_a.h, che è fuori dal nostro controllo.

others_a.h

namespace others {
struct A {
    struct Nested {
        Nested(int i) :i(i) {}
        int i{};
        void print() const { std::cout << i << std::endl; }
    };
};
}

my_class.h

#ifndef MY_CLASS_CPP
// A is actually a class
namespace others { namespace A { class Nested; } }
#endif

class MyClass {
public:
    MyClass(int i);
    ~MyClass();
    void print() const;
private:
    std::unique_ptr<others::A::Nested> _aNested;
};

my_class.cpp

#include "others_a.h"
#define MY_CLASS_CPP // Must before include my_class.h
#include "my_class.h"

MyClass::MyClass(int i) :
    _aNested(std::make_unique<others::A::Nested>(i)) {}
MyClass::~MyClass() {}
void MyClass::print() const {
    _aNested->print();
}

1
Potrebbe funzionare, ma non è documentato. Il motivo per cui funziona è che a::bè alterato allo stesso modo, non importa se aè classe o spazio dei nomi.
Jaskmar,

3
Non funziona con Clang o GCC. Dice che la classe esterna è stata dichiarata come qualcosa di diverso da uno spazio dei nomi.
Dugi

1

Non chiamerei questa una risposta, ma comunque una scoperta interessante: se ripeti la dichiarazione della tua struttura in uno spazio dei nomi chiamato C, tutto va bene (almeno in gcc). Quando viene trovata la definizione di classe di C, sembra sovrascrivere silenziosamente 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
L'ho provato con cygwin gcc e non viene compilato se si tenta di fare riferimento a A.someField. C :: D nella definizione di classe A in realtà fa riferimento alla struttura (vuota) nello spazio dei nomi, non alla struttura nella classe C (BTW questo non viene compilato in MSVC)
Dolphin

Dà l'errore: "'classe C' redeclared come diverso tipo di simbolo"
Calmarius

9
Sembra un bug di GCC. Sembra che un nome di spazio dei nomi possa nascondere un nome di classe nello stesso ambito.
Johannes Schaub - litb

0

Questa sarebbe una soluzione alternativa (almeno per il problema descritto nella domanda - non per il problema reale, ovvero 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
}

0

Se hai accesso per modificare il codice sorgente delle classi C e D, puoi estrarre la classe D separatamente e inserire un sinonimo nella classe C:

class CD {

};

class C {
public:

    using D = CD;

};

class CD;
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.