c++ templates circular-dependency

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.