the template guide geeksforgeeks example c++ oop templates specialization

guide - template specialization c++



¿Es la especialización de plantilla de clase parcial la respuesta a este problema de diseño? (4)

Digamos que tiene una clase cuyo trabajo es conectarse a un servidor remoto. Quiero abstraer esta clase para proporcionar dos versiones, una que se conecte a través de UDP y la otra a través de TCP. Quiero construir el código de tiempo de ejecución más simple posible y, en lugar de utilizar el polimorfismo, estoy considerando plantillas. Esto es lo que estoy imaginando, pero no estoy seguro de que sea la mejor forma de hacerlo:

class udp {}; class tcp {}; template<class T,typename X> class service { private: // Make this private so this non specialized version can''t be used service(); }; template<typename X> class service<udp, X> { private: udp _udp; X _x; }; template<typename X> class service<tcp, X> { private: tcp _tcp; X _x; };

Entonces, el beneficio final es que el carácter genérico de T todavía está disponible, pero se ha especializado el código muy diferente requerido para configurar una conexión UDP o TCP. Supongo que podría incluirlo en una sola clase o proporcionar otra clase que se adhiera a alguna interfaz virtual pura para configurar la conexión de red, como IConnectionManager.

Pero esto deja el problema del código para el T genérico que ahora tiene que escribirse y mantenerse en ambas versiones especializadas, donde en última instancia son las mismas. ¿Cuál es la mejor manera de abordar esto? Tengo la sensación de que todo esto va mal.


Creo que el punto principal para elegir entre polimorfismo o especialización de plantilla, al menos en este caso particular, es si desea elegir qué comportamiento usar en tiempo de ejecución o de compilación.
Si desea tener una conexión udp o tcp basada, por ejemplo, en una cadena de conexión proporcionada al usuario, entonces el polimorfismo se adapta mejor a sus necesidades; cree una clase concreta y luego páselo al código genérico que maneja un puntero a una interfaz base.
De lo contrario, podría considerar el uso de plantillas. No estoy seguro de si necesita especialización en plantillas.

Espero que esto ayude :)


Usaría el curioso patrón de plantilla recuring, también conocido como Five Point Palm Exploding Alexandrescu Technique:

template <typename Underlying> class Transmit { public: void send(...) { _U.send(...) }; private: Underlying _U; }; class Tcp { public: void send(...) {}; }; class Udp { public: void send(...) {}; };

Probablemente habrá muchos más parámetros de plantilla y subclases pero se entiende la idea, también puede usar métodos estáticos.

Por cierto, el código de plantilla generalmente es más eficiente pero también mucho más grande.


Las plantillas no son necesarias (aunque una posible solución). Esto es solo inyección de dependencia a través de plantillas en lugar de a través de un constructor. Personalmente, lo haría a través de un constructor. Pero hacerlo a través de una plantilla le da el beneficio dudoso de una llamada de método más barata (no necesita ser virtual). Pero también permite una optimización del compilador más sencilla.

Tanto los objetos udp como tcp aún deben admitir la misma interfaz .
Si lo haces a través de la herencia, ambos deben implementar una interfaz común (clase base virtual), se hace a través de plantillas, esto no es necesario, pero el compilador comprobará que admite las mismas llamadas a métodos que requiere el objeto de servicio.

Como se preguntó en la pregunta original, no veo ninguna necesidad (o beneficio) explícita para la especialización parcial de la plantilla (en la situación que se describe).

Método de plantilla

class udp {/*Interface Plop*/static void plop(Message&);}; class tcp {/*Interface Plop*/static void plop(Message&);}; template<typename T> class Service { public: void doPlop(Message& m) { T::plop(m);} // Do not actually need to store an object if you make the methods static. // Alternatively: public: void doPlop(Message& m) { protocol.plop(m);} private: T protocol; };

Versión polimórfica

class Plop{virtual void plop(Message&) = 0;} // Destruct or omitted for brevity class upd:public Plop {/*Interface Plop*/void plop(Message&);}; class tcp:public Plop {/*Interface Plop*/void plop(Message&);}; class Service { public: Service(Plop& p):protocol(p) {}; void doPlop(Message& m) { protocol.plop(m);} private: Plop& protocol; };


Esto se puede hacer mejor utilizando una política para el protocolo de transporte:

template<typename Transport> class service : Transport { public: typedef Transport transport_type; // common code void do_something() { this->send(....); } }; class tcp { public: void send(....) { } }; class udp { public: void send(....) { } }; typedef service<tcp> service_tcp; typedef service<udp> service_udp;

Tenga en cuenta que esto también es polimórfico. Se llama polimorfismo en tiempo de compilación. Poner la política en una clase base se beneficiará de la Optimización de clase base vacía. Es decir, su clase base no necesita tomar ningún espacio. Poner la política como miembro tiene el otro inconveniente de que siempre tienes que delegar cosas a ese miembro, lo que puede volverse molesto con el tiempo. El libro Modern C ++ Design describe este patrón en profundidad.

Idealmente, el protocolo de transporte no necesita saber nada sobre el protocolo anterior. Pero si por algún motivo tiene que obtener información al respecto, puede usar el wiki del patrón crtp:

template<template<typename Service> class Transport> class service : Transport<service> { // since we derive privately, make the transport layer a friend of us, // so that it can cast its this pointer down to us. friend class Transport<service>; public: typedef Transport<service> transport_type; // common code void do_something() { this->send(....); } }; template<typename Service> class tcp { public: void send(....) { } }; template<typename Service> class udp { public: void send(....) { } }; typedef service<tcp> service_tcp; typedef service<udp> service_udp;

No tiene que poner sus plantillas en los encabezados. Si los instancia de forma explícita, obtendrá tiempos de compilación más rápidos, ya que habrá que incluir menos código. Pon esto en service.cpp:

template class service<tcp>; template class service<udp>;

Ahora, el código que usa el servicio no necesita conocer el código de servicio de la plantilla, ya que ese código ya se generó en el archivo objeto de service.cpp.