2009-07-31 17 views
21

Estoy tratando de entender cómo aplicar la programación de plantillas (y en algún punto futuro, metaprogramación de plantillas) a escenarios del mundo real. Un problema que estoy encontrando es que las plantillas de C++ y el polimorfismo no siempre juegan juntos de la manera que quiero.¿Se puede utilizar el polimorfismo de plantilla en lugar del polimorfismo de OO?

Mi pregunta es si la forma en que estoy tratando de aplicar la programación de la plantilla es incorrecta (y debería usar el viejo OOP normal) o si todavía estoy atrapado en el modo de pensar OOP.

En este caso particular, estoy tratando de resolver un problema utilizando el patrón de estrategia. Me sigo encontrando con el problema donde termino deseando que algo se comporte de forma polimórfica, que las plantillas no parecen soportar.

programación orientada a objetos utilizando la composición:

class Interpolator { 
    public: 
    Interpolator(ICacheStrategy* const c, IDataSource* const d); 
    Value GetValue(const double); 
} 

void main(...) { 
    Interpolator* i; 
    if(param==1) 
     i = new Interpolator(new InMemoryStrategy(...), new TextFileDataSource(...)); 
    else if(param==2) 
     i = new Interpolator(new InMemoryStrategy(...), new OdbcDataSource(...)); 
    else if(param==3) 
     i = new Interpolator(new NoCachingStrategy(...), new RestDataSource(...)); 

    while(run) { 
     double input = WaitForRequest(); 
     SendRequest(i->GetValue(input)); 
    } 
} 

Potencial Plantilla Versión:

class Interpolator<class TCacheStrategy, class TDataSource> { 
    public: 
    Interpolator(); 
    Value GetValue(const double);    //may not be the best way but 
    void ConfigCache(const& ConfigObject);  //just to illustrate Cache/DS   
    void ConfigDataSource(const& ConfigObject); //need to configured 

} 

//Possible way of doing main? 
void main(...) { 
    if(param==1) 
     DoIt(Interpolator<InMemoryStrategy,TextFileDataSource>(),c,d); 
    else if(param==2) 
     DoIt(Interpolator<InMemoryStrategy,OdbcDataSource>(),c,d) 
    else if(param==3) 
     DoIt(Interpolator<NoCachingStrategy,RestDataSource>(),c,d) 

} 

template<class T> 
void DoIt(const T& t, ConfigObject c, ConfigObject d) { 
    t.ConfigCache(c); 
    t.ConfigDataSource(c); 
    while(run) { 
     double input = WaitForRequest(); 
     SendRequest(t.GetValue(input)); 
    } 
} 

Cuando intento para convertir la aplicación orientada a objetos a una aplicación basada en la plantilla, el código del interpolador se puede traducir sin Mucho dolor. Básicamente, reemplace las "interfaces" con los parámetros del tipo de plantilla y agregue un mecanismo para pasar una instancia de Strategy/DataSource o parámetros de configuración.

Pero cuando llego al "principal", no me queda claro cómo se debe escribir para aprovechar las plantillas en el estilo de la meta programación de plantillas. A menudo quiero usar el polimorfismo, pero no parece funcionar bien con las plantillas (a veces, parece que necesito los genéricos de borrado de tipos de Java ... ugh).

Cuando a menudo encuentro que quiero hacer es tener algo como TemplateType<?,?> x = new TemplateType<X,Y>() donde a x no le importa lo que X, Y es.

De hecho, este es a menudo mi problema cuando uso plantillas.

  1. ¿Debo aplicar un nivel más de plantillas ?
  2. ¿Estoy tratando de usar mi nueva y brillante llave de plantilla de energía en para instalar un clavo OOP en una ranura PCI?
  3. ¿O solo estoy pensando en esto todo incorrecto cuando se trata de la programación de la plantilla ?

[Editar] Algunas personas han señalado que esto no es realmente una metaprogramación de plantillas, así que he reformulado la pregunta ligeramente. Tal vez eso sea parte del problema: todavía tengo idea de lo que realmente es TMP.

Respuesta

23

Las plantillas proporcionan polimorfismo estático: especifica un parámetro de plantilla en tiempo de compilación implementando la estrategia. No proporcionan polimorfismo dinámico, donde se proporciona un objeto en tiempo de ejecución con funciones de miembros virtuales que implementan la estrategia.

Su código de plantilla de ejemplo creará tres clases diferentes, cada una de las cuales contiene todo el código Interpolator, compilado usando diferentes parámetros de plantilla y posiblemente incluyéndolo en código. Probablemente eso no es lo que quiere del POV del tamaño del código, aunque no hay nada categóricamente incorrecto en él. Supongamos que está optimizando para evitar la sobrecarga de llamada de función, entonces podría ser una mejora en el polimorfismo dinámico. Es más probable que sea excesivo. Si desea utilizar el patrón de estrategia de forma dinámica, no necesita plantillas, solo realice llamadas virtuales donde sea relevante.

No puede tener una variable del tipo MyTemplate<?> (excepto que aparece en otra plantilla antes de que se cree una instancia). MyTemplate<X> y MyTemplate<Y> son clases completamente no relacionadas (incluso si X e Y están relacionados), que tal vez tengan funciones similares si se crean instancias de la misma plantilla (que no necesitan ser, una podría ser una especialización). Incluso si lo son, si el parámetro de la plantilla está involucrado en las firmas de cualquiera de las funciones miembro, entonces esas funciones no son las mismas, simplemente tienen los mismos nombres. Entonces, desde el punto de vista del polimorfismo dinámico, las instancias de la misma plantilla están en la misma posición que dos clases cualquiera: solo pueden jugar si les da una clase base común con algunas funciones de miembros virtuales.

Por lo tanto, se puede definir una clase base común:

class InterpolatorInterface { 
public: 
    virtual Value GetValue(const double) = 0; 
    virtual void ConfigCache(const& ConfigObject) = 0; 
    virtual void ConfigDataSource(const& ConfigObject) = 0; 
    virtual ~InterpolatorInterface() {} 
}; 

continuación:

template <typename TCacheStrategy, typename TDataSource> 
class Interpolator: public InterpolatorInterface { 
    ... 
}; 

Ahora que está utilizando plantillas para crear sus diferentes tipos de interpolador de acuerdo a lo que se conoce en tiempo de compilación (por lo tanto, las llamadas desde el interpolador a las estrategias no son virtuales), y está utilizando el polimorfismo dinámico para tratarlas igual aunque no sepa hasta el tiempo de ejecución cuál quiere (de modo que las llamadas del cliente al interpolador son virtual). Solo tienes que recordar que las dos son técnicas completamente independientes, y las decisiones sobre dónde usarlas están más o menos relacionadas.

Por cierto, esto no es meta-programación de plantilla, es solo el uso de plantillas.

Editar. En cuanto a lo que es TMP, aquí está el ejemplo introductorio canónico:

#include <iostream> 

template<int N> 
struct Factorial { 
    static const int value = N*Factorial<N-1>::value; 
}; 

template<> 
struct Factorial<0> { 
    static const int value = 1; 
}; 

int main() { 
    std::cout << "12! = " << Factorial<12>::value << "\n"; 
} 

¡Observe eso! ha sido calculado por el compilador, y es una constante en tiempo de compilación. Esto es emocionante porque resulta que el sistema de plantillas C++ es un lenguaje de programación completo de Turing, que el preprocesador C no lo es. Sujeto a los límites de recursos, puede hacer cálculos arbitrarios en tiempo de compilación, evitando gastos indirectos de tiempo de ejecución en situaciones donde conoce las entradas en tiempo de compilación. Las plantillas pueden manipular sus parámetros de plantilla como un lenguaje funcional, y los parámetros de plantilla pueden ser enteros o tipos. O funciones, aunque esas no pueden ser "llamadas" en tiempo de compilación. O a otras plantillas, aunque esas no se pueden "devolver" como miembros estáticos de una estructura.

+3

+1 para señalar que esto no es meta-programación de plantilla. –

7

Encuentro que las plantillas y el polimorfismo funcionan bien juntos. En su ejemplo, si al código del cliente no le importa qué parámetros de la plantilla utiliza Interpolator, introduzca una clase base abstracta que las subclases de la plantilla. Por ejemplo:

class Interpolator 
{ 
public: 
    virtual Value GetValue (const double) = 0; 
}; 

template<class TCacheStrategy, class TDataSource> 
class InterpolatorImpl : public Interpolator 
{ 
public: 
    InterpolatorImpl(); 
    Value GetValue(const double); 
}; 

void main() 
{ 
    int param = 1; 

    Interpolator* interpolator = 0; 

    if (param==1) 
     interpolator = new InterpolatorImpl<InMemoryStrategy,TextFileDataSource>(); 
    else if (param==2) 
     interpolator = new InterpolatorImpl<InMemoryStrategy,OdbcDataSource>(); 
    else if (param==3) 
     interpolator = new InterpolatorImpl<NoCachingStrategy,RestDataSource>(); 

    while (true) 
    { 
     double input = WaitForRequest(); 
     SendRequest(interpolator->GetValue (input)); 
    } 
} 

Utilizo este idioma bastante. Oculta muy bien las cosas de la plantilla del código del cliente.

Nota, no estoy seguro de que este uso de plantillas realmente clasifique como "meta-programación". Por lo general, me reservo ese término grandioso para el uso de trucos de plantillas en tiempo de compilación más sofisticados, especialmente el uso de condicionales, definiciones recursivas, etc. para computar eficazmente cosas en tiempo de compilación.

+0

pero al hacerlo, se pierden la mayoría de los beneficios del uso de plantillas en primer lugar. – jalf

+0

@jalf: eso no es cierto, aunque entiendo de dónde vienes. En esta respuesta, el uso de plantillas permite que cada instancia de InterpolatorImpl sea optimizada en tiempo de compilación (alineación, eliminación de código muerto, desenrollado de bucles, etc.), solo es necesario despachar virtualmente la llamada GetValue de nivel superior, y todas las complejidades de implementación son más eficiente. Si eso vale algo depende completamente de cuán compleja sea esa implementación. Una buena combinación de plantillas y despacho virtual permite al programador ajustar un equilibrio óptimo. –

6

Las plantillas a veces se llaman polimorfismo estático (o tiempo de compilación), por lo que sí, a veces se pueden utilizar en lugar de polimorfismo OOP (dinámico). Por supuesto, requiere que los tipos se determinen en tiempo de compilación, en lugar de en tiempo de ejecución, por lo que no puede reemplazar por completo el polimorfismo dinámico.

Cuando a menudo encuentro que quiero hacer es tener algo como TemplateType x = new TemplateType() donde a x no le importa lo que X, Y es.

Sí, eso no es posible. Tienes que hacer algo similar a lo que tienes con la función DoIt(). A menudo, creo que termina siendo una solución más limpia de todos modos (terminas con funciones más pequeñas que hacen una sola cosa, generalmente algo bueno). Pero si los tipos solo se determinan en tiempo de ejecución (como con i en la versión OOP de su función principal), las plantillas no funcionarán.

Pero en este caso, creo que la versión de su plantilla resuelve bien el problema, y ​​es una buena solución en sí misma. (Aunque como onebyone menciona, significa código se crea una instancia para las tres plantillas, podría en algunos casos puede ser un problema)

+0

Creo que señalar específicamente que las plantillas son otra forma de "polimorfismo" en realidad hace que su aplicación sea un poco más clara para mí. –

+0

@James: un poco de un enchufe desvergonzado, pero si no ha tenido claro al respecto puede que quiera leer mi respuesta aquí - http://stackoverflow.com/questions/5854581/polymorphism-in-c/5854862#5854862 –