2008-10-26 10 views
14

En mi lugar de trabajo, que tienden a usar iostream, cadena, vector, mapa, y los impares algoritmo o dos. No hemos encontrado muchas situaciones en las que las técnicas de plantillas fueran la mejor solución para un problema.¿Dónde le parecen útiles las plantillas?

Lo que estoy buscando aquí son ideas y, opcionalmente, un código de muestra que muestra cómo utilizó una técnica de plantilla para crear una nueva solución a un problema que encontró en la vida real.

Como soborno, esperamos un voto por su respuesta.

Respuesta

7

he utilizado una gran cantidad de código de la plantilla, sobre todo en Boost y el TEL, pero rara vez he tenido la necesidad de escribir ninguna.

Una de las excepciones, hace algunos años, era un programa que manipulaba archivos EXE de formato PE de Windows. La compañía quería agregar soporte de 64 bits, pero la clase ExeFile que había escrito para manejar los archivos solo funcionaba con los de 32 bits. El código requerido para manipular la versión de 64 bits era esencialmente idéntico, pero necesitaba usar un tipo de dirección diferente (64 bits en lugar de 32 bits), lo que causaba que otras dos estructuras de datos también fueran diferentes.

Basado en el uso de la STL de una sola plantilla para apoyar tanto std::string y std::wstring, decidí intentar hacer ExeFile una plantilla, con las estructuras de datos diferentes y el tipo de dirección como parámetros.Había dos lugares donde todavía tenía que usar las líneas #ifdef WIN64 (requisitos de procesamiento ligeramente diferentes), pero no era realmente difícil de hacer. Ahora tenemos soporte total de 32 y 64 bits en ese programa, y ​​el uso de la plantilla significa que cada modificación que hemos hecho se aplica automáticamente a ambas versiones.

+0

Ya. excelente ejemplo. Gracias. – EvilTeach

10

información general sobre las plantillas:

Las plantillas son útiles en cualquier momento que necesita usar el mismo código, pero que operan en diferentes tipos de datos, donde se sabe que los tipos en tiempo de compilación. Y también cuando tienes cualquier tipo de objeto contenedor.

Un uso muy común es para casi cualquier tipo de estructura de datos. Por ejemplo: listas enlazadas, listas doblemente enlazadas, árboles, intentos, tablas hash, ...

Otro uso muy común es para los algoritmos de clasificación.

Una de las principales ventajas del uso de plantillas es que puede eliminar la duplicación de código. La duplicación de código es una de las cosas más importantes que debe evitar al programar.

Puede implementar una función Máx como una macro o una plantilla, pero la implementación de la plantilla sería segura y, por lo tanto, mejor.

Y ahora en las cosas interesantes:

Véase también template metaprogramming, que es una forma de pre-evaluación de código en tiempo de compilación en lugar de en tiempo de ejecución. La metaprogramación de plantillas solo tiene variables inmutables y, por lo tanto, sus variables no pueden cambiar. Debido a esta plantilla, la metaprogramación puede verse como un tipo de programación funcional.

Consulte este ejemplo de metaprogramación de plantillas de Wikipedia. Muestra cómo se pueden usar las plantillas para ejecutar código en tiempo de compilación. Por lo tanto, en tiempo de ejecución tiene una constante precalculada.

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

template <> 
struct Factorial<0> 
{ 
    enum { value = 1 }; 
}; 

// Factorial<4>::value == 24 
// Factorial<0>::value == 1 
void foo() 
{ 
    int x = Factorial<4>::value; // == 24 
    int y = Factorial<0>::value; // == 1 
} 
+1

plantillas de modo, que ha utilizado para factoriales en la vida real? – Roddy

+0

No en la vida real :) Pero la idea se puede expandir a muchas áreas de la vida real. –

+0

Entonces, ¿tiene un ejemplo que haya usado PERSONALMENTE? – Roddy

2

Las plantillas que normalmente consumo son una multitud de clases de contenedor, impulso los punteros inteligentes, scopeguards, algunos algoritmos STL. plantillas

Los escenarios en los que he escrito:

  • contenedores personalizados
  • de gestión de memoria, ejecución la seguridad de tipos y la invocación ctor/Dtor en la parte superior de vacío asignadores *
  • común de aplicación de sobrecargas Wiht diferente tipos, por ejemplo

    ContainsNan bool (float *, int) bool ContainsNan (doble *, int)

la que ambos sólo llamar a un (escondida local) función auxiliar

template <typename T> 
bool ContainsNanT<T>(T * values, int len) { ... actual code goes here } ; 

algoritmos específicos que son independientes del tipo, siempre que el tipo tenga ciertas propiedades, por ej. serialización binaria.

template <typename T> 
void BinStream::Serialize(T & value) { ... } 

// to make a type serializable, you need to implement 
void SerializeElement(BinStream & strean, Foo & element); 
void DeserializeElement(BinStream & stream, Foo & element) 

A diferencia de las funciones virtuales, las plantillas permiten realizar más optimizaciones.


En general, las plantillas permiten implementar un concepto o algoritmo para una multitud de tipos, y tienen las diferencias ya resueltas en tiempo de compilación.

1

Utilizo plantillas para especificar tipos de objetos de funciones. A menudo escribo código que toma un objeto de función como argumento -una función para integrar, una función para optimizar, etc.- y creo que las plantillas son más convenientes que la herencia. Por lo tanto, mi código que recibe un objeto de función, como un integrador u optimizador, tiene un parámetro de plantilla para especificar el tipo de objeto de función en el que opera.

7

Un lugar en el que utilizo plantillas para crear mi propio código es implementar las clases de políticas descritas por Andrei Alexandrescu en Modern C++ Design. Actualmente estoy trabajando en un proyecto que incluye un conjunto de clases que interactúan con el monitor BEA \ h \ h \ h Tuxedo TP de Oracle.

Una instalación que ofrece es Tuxedo colas persistentes transaccionales, así que tengo una TpQueue clase que interactúa con la cola:

class TpQueue { 
public: 
    void enqueue(...) 
    void dequeue(...) 
    ... 
} 

Sin embargo, como la cola es transaccional tengo que decidir cuál es el comportamiento de transacciones que quiero; esto podría hacerse por separado fuera de la clase TpQueue, pero creo que es más explícito y menos propenso a errores si cada instancia de TpQueue tiene su propia política en las transacciones.Así que tengo un conjunto de clases TransactionPolicy como:

class OwnTransaction { 
public: 
    begin(...) // Suspend any open transaction and start a new one 
    commit(..) // Commit my transaction and resume any suspended one 
    abort(...) 
} 

class SharedTransaction { 
public: 
    begin(...) // Join the currently active transaction or start a new one if there isn't one 
    ... 
} 

Y la clase TpQueue se re-escribir como

template <typename TXNPOLICY = SharedTransaction> 
class TpQueue : public TXNPOLICY { 
    ... 
} 

Así interior TpQueue puedo llamar begin(), abortar(), commit (), según sea necesario, pero puede cambiar el comportamiento basado en la forma en que declare la instancia:

TpQueue<SharedTransaction> queue1 ; 
TpQueue<OwnTransaction> queue2 ; 
2

utilizamos COM y aceptamos un puntero a un objeto que se puede aplicar cualquiera otra interfaz directamente oa través de [IServiceProvider] (http://msdn.microsoft.com/en-us/library/cc678965(VS.85).aspx)) esto me impulsó a crear esta función auxiliar tipo fundido.

// Get interface either via QueryInterface of via QueryService 
template <class IFace> 
CComPtr<IFace> GetIFace(IUnknown* unk) 
{ 
    CComQIPtr<IFace> ret = unk; // Try QueryInterface 
    if (ret == NULL) { // Fallback to QueryService 
     if(CComQIPtr<IServiceProvider> ser = unk) 
      ser->QueryService(__uuidof(IFace), __uuidof(IFace), (void**)&ret); 
    } 
    return ret; 
} 
1

Las razones obvias (como la prevención de la duplicación de código mediante una operación en diferentes tipos de datos) a un lado, no es este patrón genial que se llama diseño basado en la política. He hecho una pregunta sobre policies vs strategies.

Ahora, qué es tan ingenioso acerca de esta característica. Considere que está escribiendo una interfaz para que otros la usen. Usted sabe que su interfaz será utilizada, porque es un módulo en su propio dominio. Pero aún no sabes cómo las personas van a usarlo. El diseño basado en políticas fortalece su código para su reutilización futura; lo hace independiente de los tipos de datos en los que se basa una implementación particular. El código está simplemente "sorbido". :-)

Los rasgos son en sí una idea maravillosa. Pueden adjuntar comportamiento particular, datos y tipos de datos a un modelo. Los rasgos permiten la parametrización completa de estos tres campos. Y lo mejor de todo, es una muy buena manera de hacer código reutilizable.

4

Usé plantillas (con la ayuda de Boost.Fusion) para lograr enteros seguros para tipos para una biblioteca de hipergramas que estaba desarrollando. Tengo una identificación (hiper) de borde y una ID de vértice, ambos enteros. Con las plantillas, los identificadores de vértice y de hyperedge se convirtieron en diferentes tipos y el uso de uno cuando el otro se esperaba generó un error en tiempo de compilación. Me ahorró muchos dolores de cabeza que de lo contrario tendría con la depuración en tiempo de ejecución.

+0

Interesante. He hecho algo similar utilizando la herencia de una clase base con un único miembro 'int' protegido, ¡pero una clase de plantilla me hubiera ahorrado un poco de duplicación de código si lo hubiera pensado! – Roddy

3

Aquí hay un ejemplo de un proyecto real. Tengo funciones getter como esta:

bool getValue(wxString key, wxString& value); 
bool getValue(wxString key, int& value); 
bool getValue(wxString key, double& value); 
bool getValue(wxString key, bool& value); 
bool getValue(wxString key, StorageGranularity& value); 
bool getValue(wxString key, std::vector<wxString>& value); 

Y luego una variante con el valor 'predeterminado'. Devuelve el valor para la clave si existe, o el valor predeterminado si no lo hace. La plantilla me salvó de tener que crear 6 funciones nuevas por mi cuenta.

template <typename T> 
T get(wxString key, const T& defaultValue) 
{ 
    T temp; 
    if (getValue(key, temp)) 
     return temp; 
    else 
     return defaultValue; 
} 
+0

muy cierto. Hice exactamente lo mismo para wxConfig :) –

1

Una vez que vi el siguiente código:

void doSomethingGeneric1(SomeClass * c, SomeClass & d) 
{ 
    // three lines of code 
    callFunctionGeneric1(c) ; 
    // three lines of code 
} 

repitió diez veces:

void doSomethingGeneric2(SomeClass * c, SomeClass & d) 
void doSomethingGeneric3(SomeClass * c, SomeClass & d) 
void doSomethingGeneric4(SomeClass * c, SomeClass & d) 
// Etc 

Cada función tiene los mismos 6 líneas de texto de código/pegado, y cada vez que llama a otro function callFunctionGenericX con el mismo sufijo de número.

No hubo forma de refactorizar todo el conjunto. Así que mantuve la refactorización local.

he cambiado el código de esta manera (de memoria):

template<typename T> 
void doSomethingGenericAnything(SomeClass * c, SomeClass & d, T t) 
{ 
    // three lines of code 
    t(c) ; 
    // three lines of code 
} 

y modificar el código existente con:

void doSomethingGeneric1(SomeClass * c, SomeClass & d) 
{ 
    doSomethingGenericAnything(c, d, callFunctionGeneric1) ; 
} 

void doSomethingGeneric2(SomeClass * c, SomeClass & d) 
{ 
    doSomethingGenericAnything(c, d, callFunctionGeneric2) ; 
} 

Etc.

Esto se highjacking un poco la cosa plantilla, pero al final, supongo que es mejor que jugar con punteros de función typedefed o usar macros.

1

Ya se ha mencionado que puede usar plantillas como clases de política para hacer algo. Yo uso esto mucho.

También los utilizo, con la ayuda de mapas de propiedades (see boost site for more information on this), para acceder a los datos de forma genérica. Esto le da la oportunidad de cambiar la forma en que almacena los datos, sin tener que cambiar la forma de recuperarlos.

1

Personalmente he usado el Patrón de Plantilla Curiosamente Recurrente como un medio para aplicar algún tipo de diseño descendente y aplicación ascendente. Un ejemplo sería una especificación para un manejador genérico donde ciertos requisitos tanto en la forma como en la interfaz se aplican a los tipos derivados en tiempo de compilación. Se ve algo como esto:

template <class Derived> 
struct handler_base : Derived { 
    void pre_call() { 
    // do any universal pre_call handling here 
    static_cast<Derived *>(this)->pre_call(); 
    }; 

    void post_call(typename Derived::result_type & result) { 
    static_cast<Derived *>(this)->post_call(result); 
    // do any universal post_call handling here 
    }; 

    typename Derived::result_type 
    operator() (typename Derived::arg_pack const & args) { 
    pre_call(); 
    typename Derived::result_type temp = static_cast<Derived *>(this)->eval(args); 
    post_call(temp); 
    return temp; 
    }; 

};

Algo como esto puede utilizarse entonces para asegurarse de que sus controladores se derivan de esta plantilla y hacer cumplir el diseño de arriba hacia abajo y luego permitir abajo hacia arriba personalización:

struct my_handler : handler_base<my_handler> { 
    typedef int result_type; // required to compile 
    typedef tuple<int, int> arg_pack; // required to compile 
    void pre_call(); // required to compile 
    void post_call(int &); // required to compile 
    int eval(arg_pack const &); // required to compile 
}; 

Este continuación, le permite tener polimórfica genérica funciones que tienen que ver sólo con handler_base <> tipos derivados:

template <class T, class Arg0, class Arg1> 
typename T::result_type 
invoke(handler_base<T> & handler, Arg0 const & arg0, Arg1 const & arg1) { 
    return handler(make_tuple(arg0, arg1)); 
}; 
Cuestiones relacionadas