c++ - Resolver una dependencia circular entre clases de plantillas
templates circular-dependency (4)
(Actualizado) Debería poder manejar eso de la misma manera que con las clases que no son de plantilla. Escribe tu Bar.h así. (Y de manera similar para Foo.h)
#if !defined(BAR_H_INCLUDED)
#define BAR_H_INCLUDED
template <class T>
class Foo;
template <class T>
class Bar
{
/// Declarations, no implementations.
}
#include "Foo.h"
template <class T>
Base* Bar<T>::Convert() { /* implementation here... */ }
#endif
Tengo dos clases, Foo<T>
y Bar<T>
, derivadas de Base
. Cada una anula un método virtual Base* convert(ID) const
, donde ID
es una instancia de un tipo que identifica de forma única una instanciación particular de Foo
o Bar
(simule que es una enum
). El problema es que Foo::convert()
necesita poder devolver una instancia de Bar
, y también Bar::convert()
necesita poder instanciar a Foo
. Como ambas son plantillas, esto da como resultado una dependencia circular entre Foo.h
y Bar.h
¿Cómo resuelvo esto?
Edición: una declaración de reenvío no funciona porque la implementación de cada método necesita el constructor de la otra clase:
Foo.h
:
#include <Base.h>
template<class T> class Bar;
template<class T>
class Foo : public Base { ... };
template<class T>
Base* Foo<T>::convert(ID id) const {
if (id == BAR_INT)
return new Bar<int>(value); // Error.
...
}
Bar.h
:
#include <Base.h>
template<class T> class Foo;
template<class T>
class Bar : public Base { ... };
template<class T>
Base* Bar<T>::convert(ID id) const {
if (id == FOO_FLOAT)
return new Foo<float>(value); // Error.
...
}
El error es, naturalmente, "uso no válido de tipo incompleto".
Debes usar declaraciones de clase de plantilla en cualquiera de los encabezados.
template <class T>
class X;
Es perfectamente buena declaración de clase de plantilla.
La respuesta de James Curran es una bendición. En términos generales, la idea de James es restringir la inclusión de los archivos de encabezado requeridos hasta el momento en que se necesiten los miembros (''declaraciones) que provienen de los archivos de encabezado incluidos. Como ejemplo:
t1.hh
#ifndef S_SIGNATURE
#define S_SIGNATURE
struct G; // forward declaration
template<typename T>
struct S {
void s_method(G &);
};
#include "t2.hh" // now we only need G''s member declarations
template<typename T>
void S<T>::s_method(G&g) { g.g_method(*this); }
#endif
t2.hh
#ifndef G_SIGNATURE
#define G_SIGNATURE
template<typename T>
struct S; // forward declaration
struct G {
template<typename T>
void g_method(S<T>&);
};
#include "t1.hh" // now we only need S'' member declarations
template<typename T>
void G::g_method(S<T>& s) { s.s_method(*this); }
#endif
t.cc
#include "t1.hh"
#include "t2.hh"
S<int> s;
G g;
int main(int argc,char**argv) {
g.g_method(s); // instantiation of G::g_method<int>(S<int>&)
}
Lo que debe hacer es separar las declaraciones de clase de la implementación. Entonces algo como
template <class T> class Foo : public Base
{
public:
Base* convert(ID) const;
}
template <class T> class Bar : public Base
{
public:
Base* convert(ID) const;
}
template <class T> Base* Foo<T>::convert(ID) const {return new Bar<T>;}
template <class T> Base* Bar<T>::convert(ID) const {return new Foo<T>;}
De esta manera, tiene definiciones de clase completas cuando se definen las funciones.