Pregunta

En mi lugar de trabajo, tendemos a usar iostream, cadena, vector, mapa, y la extraño algoritmo o dos.En realidad no hemos encontrado muchas situaciones en las que la plantilla de técnicas eran una mejor solución a un problema.

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

Como un soborno, esperar un voto por tu respuesta.

¿Fue útil?

Solución

He usado un montón de código de la plantilla, principalmente en el Impulso y la STL, pero yo rara vez he tenido la necesidad de escribir cualquier.

Una de las excepciones, hace un par de años, fue en un programa que manipula Windows PE-formato de archivos EXE.La empresa quería añadir soporte para 64-bits, pero el ExeFile de la clase que había escrito para manejar los archivos sólo se trabajó con 32-bit queridos.El código necesario para manipular la versión de 64 bits era esencialmente idéntico, pero es necesario para utilizar una dirección diferente tipo (de 64 bits en lugar de 32 bits), lo que causó dos estructuras de datos a ser diferente.

Basado en la STL de uso de una plantilla única para apoyar a ambos std::string y std::wstring, Me decidí a tratar de hacer ExeFile una plantilla, con las diferentes estructuras de datos y el tipo de dirección como parámetros.Hay dos lugares donde todavía tenía que usar #ifdef WIN64 líneas (ligeramente diferentes de los requisitos de procesamiento), pero no era realmente difícil de hacer.Tenemos full 32 y 64 bits de apoyo en ese programa ahora, y con la plantilla que significa que todas las modificaciones que hemos hecho desde automáticamente se aplica a ambas versiones.

Otros consejos

Información general sobre plantillas:

Las plantillas son útiles en cualquier momento que necesite usar el mismo código pero operando en diferentes tipos de datos, donde los tipos se conocen 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 individualmente, listas doblemente enlazadas, árboles, intentos, tablas hash, ...

Otro uso muy común es para ordenar algoritmos.

Una de las principales ventajas de usar 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.

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

Y ahora sobre las cosas interesantes:

Consulte también metaprogramación de plantillas , que es una forma de preevaluar el código en tiempo de compilación en lugar de en tiempo de ejecución. La metaprogramación de plantilla 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.

Vea 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
}

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

Una instalación que ofrece Tuxedo son las colas persistentes transaccionales, por lo que tengo una clase TpQueue que interactúa con la cola:

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

Sin embargo, como la cola es transaccional, necesito decidir qué comportamiento de transacción deseo; 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 sobre 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 reescribe como

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

Entonces dentro de TpQueue puedo llamar a begin (), abort (), commit () según sea necesario, pero puedo cambiar el comportamiento en función de la forma en que declaro la instancia:

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

Utilicé plantillas (con la ayuda de Boost.Fusion) para lograr enteros de tipo seguro para una biblioteca de hipergrafía que estaba desarrollando. Tengo una ID de (hiper) borde y una ID de vértice, los cuales son enteros. Con las plantillas, las ID de vértice e hiperedificación se convirtieron en diferentes tipos y usar una cuando se esperaba la otra generó un error en tiempo de compilación. Me ahorró muchos dolores de cabeza que de otro modo tendría con la depuración en tiempo de ejecución.

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 de la clave si existe, o el valor predeterminado si no existe. La plantilla me salvó de tener que crear 6 nuevas funciones yo mismo.

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

Las plantillas que consumo regularmente son una multitud de clases de contenedores, aumentan los punteros inteligentes, scopeguards , algunos algoritmos STL.

Escenarios en los que he escrito plantillas:

  • contenedores personalizados
  • gestión de memoria, implementación de seguridad de tipo y invocación CTor / DTor sobre asignadores de vacío *
  • implementación común para sobrecargas con diferentes tipos, por ejemplo

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

que ambos simplemente llaman una función auxiliar (local, oculta)

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, p. 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 que se realicen más optimizaciones.


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

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

// 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;
}

Utilizo plantillas para especificar tipos de objetos de función. 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 encuentro plantillas más convenientes que la herencia. Entonces, 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.

Dejando a un lado las razones obvias (como evitar la duplicación de código operando en diferentes tipos de datos), existe este patrón realmente genial que se llama diseño basado en políticas. He hecho una pregunta sobre políticas vs estrategias .

Ahora, ¿qué tiene de ingenioso 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 la gente lo va a usar. El diseño basado en políticas fortalece su código para su futura reutilización; te hace independiente de los tipos de datos en los que se basa una implementación particular. El código es solo & Quot; sorbido en & Quot ;. :-)

Los rasgos son per se una idea maravillosa. Pueden adjuntar comportamientos particulares, datos y datos de tipo a un modelo. Los rasgos permiten la parametrización completa de todos estos tres campos. Y lo mejor de todo, es una muy buena manera de hacer que el código sea reutilizable.

Una vez vi el siguiente código:

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

repetido 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 las mismas 6 líneas de código copiadas / pegadas, y cada vez que llama a otra función callFunctionGenericX con el mismo sufijo de número.

No había forma de refactorizar todo el asunto. Así que mantuve la refactorización local.

Cambié 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 modificó 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 es algo de alto nivel en el tema de la plantilla, pero al final, creo que es mejor que jugar con punteros de función typedefed o usar macros.

Personalmente, he utilizado el patrón de plantilla curiosamente recurrente como un medio de imponer alguna forma de diseño de arriba hacia abajo e implementación de abajo hacia arriba. Un ejemplo sería una especificación para un controlador genérico donde ciertos requisitos tanto en forma como en interfaz se aplican a los tipos derivados en tiempo de compilación. Se parece a 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 se puede usar para asegurarse de que sus manejadores se deriven de esta plantilla y apliquen un diseño de arriba hacia abajo y luego permitan la personalización de abajo hacia arriba:

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
};

Esto le permite tener funciones polimórficas genéricas que se ocupan solo de 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));
};

Ya se ha mencionado que puede usar plantillas como clases de políticas para hacer algo . Lo uso mucho.

También los uso, con la ayuda de mapas de propiedades ( vea el sitio de impulso para obtener más información sobre esto ), con el fin de acceder a los datos de forma genérica. Esto brinda la oportunidad de cambiar la forma en que almacena los datos, sin tener que cambiar la forma en que los recupera.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top