2010-07-15 8 views
17

Tengo el código siguiente. Tengo una clase de plantilla abstracta Foo y dos subclases (Foo1 y Foo2) que se derivan de instancias de la plantilla. Deseo utilizar punteros en mi programa que puedan apuntar a objetos de tipo Foo1 o Foo2, por lo tanto, creé una interfaz IFoo.Creación de una interfaz para una plantilla de clase abstracta en C++

Mi problema es que no estoy seguro de cómo incluir functionB en la interfaz, ya que depende de la instanciación de la plantilla. ¿Es posible hacer accesible la función B a través de la interfaz, o estoy intentando lo imposible?

Muchas gracias por su ayuda.

class IFoo { 
    public: 
     virtual functionA()=0; 

}; 

template<class T> 
class Foo : public IFoo{ 
    public: 
     functionA(){ do something; }; 
     functionB(T arg){ do something; }; 
}; 

class Foo1 : public Foo<int>{ 
... 
}; 

class Foo2 : public Foo<double>{ 
... 
}; 

Respuesta

0

No creo que pueda obtener lo que desea. Piense en esto si tuviera que implementar su sugerencia: si tiene un puntero a una instancia de IFoo y llama al functionB(), ¿qué parámetro de tipo debería darle? El problema subyacente es que Foo1::functionB y Foo2::functionB tienen diferentes firmas y hacen cosas diferentes.

+0

pero técnicamente no voy a ser la creación de una instancia de IFoo, Solo deseo crear una instancia de Foo1 (o 2) (apuntada por un puntero IFoo) y asumí que la función apropiada podría ser encontrada por el límite dinámico. – bishboshbash

+0

@bishboshbash: Para que se encuentre una función mediante vinculación dinámica, se deben conocer todos los tipos de argumentos; de lo contrario, no estaría claro qué buscar si utilizó funciones sobrecargadas. – liori

+0

Creo que entiendo. Si creara una instancia de Foo1 y llamara a Foo1.functionB (..) funcionaría bien, ya que la plantilla se ha instanciado. ¿Podría crear una clase abstracta para apuntar a los objetos de tipo Foo1 o Foo2 a través de herencia múltiple y, por lo tanto, evitar el problema del enlace dinámico que intenta pasar a través de una plantilla desinstalada? – bishboshbash

4

La manera más fácil es hacer que la interfaz se modele.

template <class T> 
class IFoo { 
    public: 
     virtual void functionA()=0; 
     virtual void functionB(T arg){ do something; }; 
}; 

template<class T> 
class Foo : public IFoo<T>{ 
    public: 
     void functionA(){ do something; }; 
     void functionB(T arg){ do something; }; 
}; 
+2

Esto significa que no podría crear punteros de tipo IFoo y hacer que apunten a instancias de Foo1 o Foo2. – bishboshbash

+2

Eso es correcto, y ese es un tema fundamental aquí. El tipo de argumento que toma 'functionB' depende del tipo de plantilla instanciada y ** debe ** conocerse en tiempo de compilación. –

+0

@bishboshbash su requisito es mal formado en primer lugar. Foo1 y Foo2 tienen diferentes clases base (IFoo y IFoo son dos tipos diferentes). – h9uest

4

Desde tipo de argumento de funciónB debe ser conocida de antemano, usted tiene sólo una opción: Que sea un tipo que puede contener cada posible argumento. Esto a veces se denomina "tipo superior" y las bibliotecas de refuerzo tienen el tipo any que se acerca bastante a lo que haría un tipo superior. Esto es lo que podría funcionar:

#include <boost/any.hpp> 
#include <iostream> 
using namespace boost; 

class IFoo { 
    public: 
    virtual void functionA()=0; 
    virtual void functionB(any arg)=0; //<-can hold almost everything 
}; 

template<class T> 
class Foo : public IFoo{ 
    public: 
     void functionA(){ }; 
     void real_functionB(T arg) 
     { 
     std::cout << arg << std::endl; 
     }; 
     // call the real functionB with the actual value in arg 
     // if there is no T in arg, an exception is thrown! 

     virtual void functionB(any arg) 
     { 
      real_functionB(any_cast<T>(arg)); 
     } 
}; 

int main() 
{ 
    Foo<int> f_int; 
    IFoo &if_int=f_int; 

    if_int.functionB(10); 

    Foo<double> f_double; 
    IFoo &if_double=f_double; 
if_int.functionB(10.0); 

} 

Desafortunadamente, any_cast no se sabe acerca de las conversiones habituales. Por ejemplo, any_cast<double>(any(123)) arroja una excepción, porque ni siquiera intenta convertir el entero 123 en un doble. Si no le importan las conversiones, porque de todos modos es imposible replicarlas todas. Entonces hay un par de limitaciones, pero es posible encontrar soluciones si es necesario.

+1

Para utilizar esta respuesta, la persona que llama ('main()') debe conocer el subtipo real de una instancia 'IFoo' (' if_int' y 'if_double') para que pueda pasar los argumentos correctos. Si se equivoca, se lanza una excepción de tiempo de ejecución. Entonces, ¿por qué no tener la llamada solo usa un 'dynamic_cast'? – Karmastan

+1

Si tengo la persona que llama para usar dynamic_cast, entonces necesito que sepa sobre Foo , Foo . Pero lo bueno de una interfaz abstracta es que no necesito decirle a nadie cómo se implementa realmente la interfaz. Puede ser un tipo privado, de terceros, de una biblioteca compartida o un tipo local en un cuerpo de función. Además, es solo la limitación de any_cast de boost que arroja para el tipo de argumento incorrecto. Usted es libre de mejorar su funcionalidad, de modo que haga las conversiones correctas para los tipos intrínsecos, por ejemplo. Sin embargo, lo que no se puede hacer es capturar ** todas las conversiones válidas. –

9

En realidad está intentando lo imposible.

El meollo del asunto es simple: virtual y template no se mezclan bien.

  • template es sobre generación de código en tiempo de compilación. Puedes pensar que es un tipo de macros con reconocimiento de tipo + algunos trucos espolvoreados para metaprogramación.
  • virtual es sobre la decisión de tiempo de ejecución, y esto requiere un poco de trabajo.

virtual generalmente se implementa utilizando tablas virtuales (consulte una tabla que enumera los métodos). El número de métodos debe conocerse en tiempo de compilación y se define en la clase base.

Sin embargo, con su requerimiento, necesitaríamos una tabla virtual de tamaño infinito, que contenga métodos para tipos que aún no hemos visto y que solo se definirá en los años venideros ... lamentablemente es imposible.

¿Y si fuera posible?

Bueno, simplemente no tendría sentido. ¿Qué sucede cuando llamo al Foo2 con un int? ¡No es para eso!Por lo tanto, se rompe el principio de que Foo2 implementa todos los métodos desde IFoo.

Por lo tanto, sería mejor si usted indicó el problema real, de esta manera podemos ayudarle a un nivel de diseño en lugar de a nivel técnico :)

+0

Tengo clases Foo1 y Foo2. Ambas contienen estructuras de datos similares: una contiene ints y una contiene números racionales (pares de ints). Tienen muchos métodos que se implementan de forma idéntica (respectiva a int/pair of int), que interactúan con otros tipos de datos (int/pair of int). Pero tienen algunos métodos que se implementan de manera diferente. Originalmente lo implementé todo con herencia, pero luego las estructuras de datos están definidas en las subclases y no puedo escribir funciones de miembros que actúen sobre ellas en la clase base, lo que conduce a grandes cantidades de duplicación de código. – bishboshbash

+0

He tenido los mismos problemas muchas veces y con clases que contienen muchos métodos y propiedades en su interfaz (10+). Lo que hice fue crear interfaces para cada instancia y reducir el número de métodos utilizando cierres, p. 'ErrorCode ScalarParameterSet (ParamType t, float ParamValue)' para unir todos los setters. Si lo desea, podría proporcionar un ejemplo de esto –

Cuestiones relacionadas