Declaración directa de tipos / clases anidados en C ++

197

Recientemente me quedé atrapado en una situación como esta:

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

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

Por lo general, puede declarar un nombre de clase:

class A;

Pero no puede reenviar la declaración de un tipo anidado, lo siguiente provoca un error de compilación.

class C::D;

¿Algunas ideas?

Calmarius
fuente
66
¿Por qué necesitas eso? Tenga en cuenta que puede reenviar la declaración si es un miembro de la misma clase que se está definiendo: clase X {clase Y; Y * a; }; clase X :: Y {};
Johannes Schaub - litb
Esta solución funcionó para mí (espacio de nombres C {clase D;};): stackoverflow.com/questions/22389784/…
Albert Wiersch
Encontré un enlace de
bitlixi

Respuestas:

224

No puedes hacerlo, es un agujero en el lenguaje C ++. Tendrás que anidar al menos una de las clases anidadas.

Adam Rosenfield
fuente
66
Gracias por la respuesta. En mi caso, no son mis clases anidadas. Tenía la esperanza de evitar una gran dependencia del archivo de encabezado de la biblioteca con una pequeña referencia hacia adelante. Me pregunto si C ++ 11 lo arregló?
Marsh Ray
61
Oh. Justo lo que no quería que apareciera Google. Gracias de todos modos por la respuesta concisa.
learnvst
19
Lo mismo aquí ... ¿Alguien sabe por qué no es posible? Parece que hay casos de uso válidos, y esta falta impide la coherencia de la arquitectura en algunas situaciones.
Maël Nison el
Puedes usar amigo. Y solo ponga un comentario en el sentido de que lo está utilizando para solucionar el agujero en C ++.
Erik Aronesty
3
Cada vez que me encuentro con defectos tan innecesarios en este lenguaje extraño, estoy dividido entre reír y llorar
SongWithoutWords
33
class IDontControl
{
    class Nested
    {
        Nested(int i);
    };
};

Necesitaba una referencia directa como:

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

Mi solución fue:

class IDontControl_Nested; // Forward reference to distinct name.

Más tarde, cuando podría usar la definición 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) { }
};

Esta técnica probablemente sería más problemática de lo que vale si hubiera constructores complicados u otras funciones miembro especiales que no se heredaran sin problemas. Me imagino que ciertas plantillas mágicas reaccionan mal.

Pero en mi caso muy simple, parece funcionar.

Marsh Ray
fuente
16
En C ++ 11 puede heredar constructores using basename::basename;en la clase derivada, por lo tanto, no hay problema con complicados ctors.
Xeo
1
Buen truco, pero no funcionará si el puntero a IDontControl :: Nested se usa dentro del mismo encabezado (donde se declaró hacia adelante) y se accede desde un código externo que también incluye la definición completa de IDontControl. (Porque el compilador no coincidirá con IDontControl_Nested e IDontControl :: Nested). La solución es realizar un reparto estático.
Artem Pisarenko
Recomiendo hacer lo contrario y tener la clase afuera, y solo usarla typedefdentro de la clase
ridderhoff
3

Si realmente quiere evitar #incluir el desagradable archivo de encabezado en su archivo de encabezado, puede hacer esto:

archivo hpp:

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

archivo cpp

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

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

Pero entonces:

  1. tendrá que especificar el tipo incrustado en el momento de la llamada (especialmente si su función no toma ningún parámetro del tipo incrustado)
  2. su función no puede ser virtual (porque es una plantilla)

Entonces, sí, compensaciones ...

Edenbridge
fuente
1
¿Qué diablos es un hpparchivo?
Naftali aka Neal
77
jajaja, un archivo de encabezado .hpp se usa en proyectos C ++ para distinguirlo de un archivo de encabezado C que generalmente termina con .h. Al trabajar con C ++ y C en el mismo proyecto, algunas personas prefieren .hpp y .cpp para archivos C ++, para hacerlo explícitamente con qué tipo de archivos están tratando, y .h y .c para archivos C.
bitek
2

Esto puede hacerse declarando la clase externa como un espacio de nombres .

Muestra: Tenemos que usar una clase anidada others :: A :: Anidado en others_a.h, que está fuera de nuestro control.

otros_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();
}
bitlixi
fuente
1
Puede funcionar, pero no está documentado. La razón por la que funciona es que a::bestá maltratada de la misma manera, sin importar si aes clase o espacio de nombres.
jaskmar
3
No funciona con Clang o GCC. Dice que la clase externa se declaró como algo diferente a un espacio de nombres.
Dugi
1

No llamaría a esto una respuesta, pero no obstante un hallazgo interesante: si repites la declaración de tu estructura en un espacio de nombre llamado C, todo está bien (al menos en gcc). Cuando se encuentra la definición de clase de C, parece sobrescribir en silencio el espacio nams 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;
}
nschmidt
fuente
1
Intenté esto con cygwin gcc y no se compila si intentas hacer referencia a A.someField. C :: D en la definición de clase A en realidad se refiere el la struct (vacío) en el espacio de nombres, no el struct en la clase C (BTW esto no se compila en MSVC)
Dolphin
Da el error: "'clase C' redeclarada como un tipo diferente de símbolo"
Calmarius
9
Parece un error de GCC. Parece pensar que un nombre de espacio de nombres puede ocultar un nombre de clase en el mismo ámbito.
Johannes Schaub - litb
0

Esta sería una solución alternativa (al menos para el problema descrito en la pregunta, no para el problema real, es decir, cuando no se tiene control sobre la definición de 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
}
chtz
fuente
0

Si tiene acceso para cambiar el código fuente de las clases C y D, puede extraer la clase D por separado e ingresar un sinónimo en la clase C:

class CD {

};

class C {
public:

    using D = CD;

};

class CD;
Suvorov Ivan
fuente