2012-02-01 22 views
5

Interfaz:C plantillas ++ polimorfismo obstáculo

template <class T> 
class Interface{ 
    public: 
    typedef T Units; 
    virtual T get() = 0; 
}; 

aplicación1:

class Implementation1: public Interface<float> { 
    public: 

    float get() { 
     return 0.0f; 
    } 

}; 

Implementation2:

class Implementation2: public Interface<int> { 
    public: 

    int get() { 
     return 0; 
    } 

}; 

Container (con errores):

class Container{ 
    private: 
    Interface* floatGetter; 
    int n; 
    Timer::Units* array; 

    public: 
    Container(Interface* floatGetter, int n) { 
     this->floatGetter= floatGetter; 
     this->n = n; 
     array = new Timer::Units[n]; 
    } 

    ~Container() { 

    } 

}; 

Para obtener más información, tengo una interfaz de plantilla y una clase derivada de esta interfaz sin plantilla. Alguna otra clase toma un objeto de la clase derivada pero toma el objeto como una interfaz (en otras palabras, inyección de dependencia). Pero el tipo de interfaz en esta clase se define mediante la implementación de la interfaz. ¿Cómo implementar esta idea en C++?

Edit1:

Ejemplo:

Interface<float> myInterface1 = new Implementation1(); 
Interface<int> myInterface2 = new Implementation2(); 
Container container1 = new Container(myInterface1, 10); 
Container container2 = new Container(myInterface2, 10); 

necesito ese contenedor entiende argumento plantilla de interfaz de su aplicación.

Respuesta

6

Bien, primero, una explicación del problema aquí. Lo que se requiere es una interfaz, que defina un método virtual, utilizado para obtener un valor con un tipo de plantilla. Como lo que queremos es una interfaz, el método get debe ser virtual. Por otro lado, nos gustaría poder devolver diferentes tipos, por lo que queremos personalizarlo. Sin embargo, no se puede templar un método virtual, porque el compilador no sabría qué instancias de ese método incluir en el vtable.

Una solución es hacer lo que se hace en la pregunta, es decir, Templetize la clase de interfaz. Una propiedad importante de los tipos de plantilla es que las diferentes instancias de la misma clase son tipos completamente diferentes. No comparten una base común, y no son convertibles entre sí. Simplemente no podemos tener un puntero Interface<Generic> dando vueltas en funciones normales, con sus métodos get() siendo llamados. Considere esto: cada instancia del tipo de plantilla de Interfaz tiene una firma diferente para el método get(). Esto significa que mientras se llama a ese método, tienen que suceder cosas diferentes en la pila. ¿Cómo podría el compilador saber qué versión del método get() llamar (cómo preparar la pila para la llamada a la función) si todo lo que tiene es un puntero Interface<Generic>?

Puedo pensar en dos soluciones generales para ese problema.

  1. Quitar toda la plantilla galimatías y hacer que el método get() devuelve un objeto de tipo-borrado, como impulso :: variante o cualquier boost ::. Corrígeme si me equivoco aquí (*), pero boost :: variant es como una unión que recuerda qué tipo de unión está asignado, mientras boost :: any es como un vacío *, pero recuerda a qué tipo apunta. . Esta ruta de solución implica dos cosas: a) Los tipos de objetos devueltos se resolverán en tiempo de ejecución, y habrá algunos gastos generales al manipular estos tipos. b) Las clases secundarias de Interface tendrán que administrar uno de estos objetos borrados por tipo, haciéndolos más complicados.

  2. Lleve la plantilla hasta el extremo y haga referencia a los objetos de la interfaz siempre en un contexto templetizado, de modo que el compilador genere las llamadas de función correctas durante las instancias de esos contextos. Di un ejemplo a continuación que sigue este camino. El ejemplo crea un contenedor para mantener juntos diferentes tipos de objetos Interface <>, al mismo tiempo que permite la aplicación de funcionalidades templetizadas (¿es correcto llamar a esto generalmente "visitantes"?). Tenga en cuenta que en ese ejemplo, los objetos de interfaz con diferentes parámetros de tipo se mantienen en diferentes std :: listas en esa clase de contenedor, por lo que en el tiempo de ejecución, no hay necesidad de resolver sus tipos.

responsabilidad: Lo que sigue es una exageración ...

He aquí cómo usted puede tener un contenedor de la "interfaz" clase de plantilla con diferentes argumentos de plantilla. He usado una lista std :: para guardar las instancias, pero puedes cambiarla.

#include<boost/fusion/container/vector.hpp> 
#include<boost/fusion/algorithm.hpp> 
#include<boost/mpl/transform.hpp> 
#include<boost/mpl/contains.hpp> 
#include<boost/utility/enable_if.hpp> 
#include<boost/type_traits/add_reference.hpp> 
#include<list> 
#include<algorithm> 
#include <iostream> 

using namespace boost; 

template <class T> 
class Interface{ 
    public: 
    typedef T Units; 
    virtual T get() = 0; 
}; 

class Implementation1: public Interface<float> { 
    public: 

    float get() { 
     return 0.0f; 
    } 

}; 

class Implementation2: public Interface<int> { 
    public: 

    int get() { 
     return 5; 
    } 

}; 

template<class element> 
struct to_list { 
    typedef std::list<Interface<element> *> type; 
}; 

template<class elementVector> 
struct to_containers { 
    typedef typename mpl::transform<elementVector,to_list<mpl::_1> >::type type; 
}; 

class Container{ 
    typedef fusion::vector<int,float> AllowedTypes; 
    typename to_containers<AllowedTypes>::type containers; 

public: 
    template<class type> typename enable_if<mpl::contains<AllowedTypes,type>,void>::type 
    /*void*/ add(Interface< type/*included in AllowedTypes*/ > & floatGetter) { 
     fusion::deref(fusion::find<typename to_list<type>::type >(containers)) 
      /*<type> container*/.push_back(&floatGetter); 
    } 

    template<class functional> 
    void apply(functional f) { 
     fusion::for_each(containers,applyFunctional<functional>(f)); 
    } 

private: 
    template<class functional> 
    struct applyFunctional { 
     functional f; 
     applyFunctional(functional f): f(f){} 
     template<class T> void operator()(T & in) const { 
      std::for_each(in.begin(), in.end(),f); 
     } 
    }; 

}; 

struct printValueFunctional { 
    template<class element> 
    void operator()(Interface<element> * in) const { 
     std::cout<<"Hi, my value is:"<<in->get()<<"\n"; 
    } 
}; 

int main() { 

    Implementation1 impl1; 
    Implementation2 impl2; 
    Interface<float> &myInterface1 = impl1; 
    Interface<int> &myInterface2 = impl2; 
    Container container; 
    container.add(myInterface1); 
    container.add(myInterface2); 
    container.apply(printValueFunctional()); 
    return 0; 
} 

Y la salida es:

Hi, my value is:5 
Hi, my value is:0 

Bueno, esto realmente es una enorme exageración para la mayoría de aplicaciones, pero se lo pidió :)

Si lo que desea es una interfaz, que puede devolver cosas diferentes, también podría considerar boost.variant. El ejemplo anterior es realmente valioso para todo el polimorfismo estático que utiliza.

EDIT: David ha señalado algo importante, podría ser un escollo, si usted, por alguna razón, asume lo contrario. Este contenedor realmente no se mantiene fiel al orden de las inserciones de elementos. El orden de sus llamadas funcionales podría no suceder en el orden de las inserciones de los elementos, es decir, asumir que la iteración será en un orden "aleatorio".

(*) impulso :: variante y cualquier impulso :: se discuten here

+1

+1 para una buena pieza de metaprogramación. No creo que sea una buena solución al problema, pero merece el representante :) –

+0

Gracias :) No creo que sea una buena solución para el problema en general, pero muestra que la metaprogramación de plantillas permite esto sin borrado de tipo. También obtienes un contenedor mixto con iteración muy rápida. – enobayram

+0

No es realmente un contenedor mixto (¿o no?) ... sino un tipo que contiene varios contenedores internamente. Para mí, la diferencia radica en el hecho de que los diferentes tipos aún están separados internamente, incluso si se tiene la impresión de que no lo son, y eso significa que mientras se borra el tipo puede mantener los invariantes del contenedor (por ejemplo, el orden de inserción). en contenedores de secuencia), no se puede hacer lo mismo con este enfoque (para ser honesto, esto es sólo una corazonada, leí el código, pero no lo he compilado/probado) –

4

Interface es una plantilla, no un tipo. Las variables en la clase debe ser la creación de instancias de la plantilla con un tipo particular, como:

class Container { 
    Interface<float> *floatGetter; 

Y del mismo modo para el argumento del constructor.

Nota al margen: su destructor debe liberar los recursos que maneja su clase.

Nota al margen 2: es bastante difícil escribir un tipo que gestione directamente más de un recurso, considere utilizar punteros inteligentes para almacenar sus datos.

Nota al costado 3: aprenda y use las listas de inicialización.

+0

Tu * * constructor debe liberar los recursos? –

+0

@jesse gracias por capturar el error tipográfico ... Por supuesto, el destructor debería liberar los recursos, en lugar del constructor. –

+0

Busque la actualización, por favor – itun