2012-01-06 13 views
16

Uso plantillas mixins en C++ mucho, pero me pregunto por qué la técnica no se usa más. Parece lo último en reutilización. Esta combinación de potencia y eficiencia es una de las razones por las que realmente amo C++ y no me veo moviéndome a un lenguaje JIT.¿Por qué las plantillas mixins en C++ no son más de un pilar?

Este artículo: http://www.thinkbottomup.com.au/site/blog/C%20%20_Mixins_-_Reuse_through_inheritance_is_good es un buen resumen si no sabe cuáles son, y expone el caso con tanta claridad en términos de reutilización y rendimiento.

+0

Los lenguajes JIT pueden ser tan potentes como C++ para todas las aplicaciones que no sean * la mayoría * de las que requieren un gran uso del procesador. – GManNickG

+4

@GMan: Creo que la referencia de JIT es irrelevante para la pregunta.Es una buena pregunta: es un patrón de diseño interesante que nunca he visto en ninguna de las bases de código en las que he trabajado. – Skizz

+0

@Skizz: es totalmente irrelevante, estoy de acuerdo; pero está ahí. – GManNickG

Respuesta

20

El problema con mixins es ... construcción.

class Base1 { public: Base1(Dummy volatile&, int); }; 

class Base2 { public: Base2(Special const&, Special const&); }; 

Y ahora, mi súper mixin:

template <typename T> 
struct Mixin: T {}; 

¿Notas el problema aquí? ¿Cómo diablos se supone que debo pasar los argumentos al constructor de la clase base? ¿Qué tipo de constructor debería indicar Mixin?

Es un problema difícil, y no se ha resuelto hasta que C++ 11 mejoró el lenguaje para obtener un reenvío perfecto.

// std::foward is in <utility> 

template <typename T> 
struct Mixin: T { 
    template <typename... Args> 
    explicit Mixin(Args&&... args): T(std::forward<Args>(args...)) {} 
}; 

Nota: los controles dobles son bienvenidos

Así ahora realmente podemos utilizar mixins ... y sólo hay que cambiar los hábitos de la gente :)

Por supuesto, si realmente querer es un tema totalmente diferente.

Uno de los problemas con mixins (que el artículo defectuoso que referencia omitir felizmente) es el aislamiento de dependencia que pierde por completo ... y el hecho de que los usuarios de LoggingTask están obligados a escribir métodos de plantilla. En bases de código muy grandes, se presta más atención a las dependencias que al rendimiento, porque las dependencias queman los ciclos humanos mientras que el rendimiento solo quema ciclos de CPU ... y esos son generalmente más baratos.

+1

Eso exagera un poco el problema práctico: puede reenviar un pequeño número finito de argumentos con un pequeño tedio (plantilla explicita Mixin (const X & x): T (x) {} plantilla explicita Mixin (X &, Y &): T (x, y) {} ...), o un gran número finito con un poco de fea invocación al preprocesador .... –

+2

@TonyDelroy: oh créanme, la gente lo ha intentado. Dadas las combinaciones de 'const',' volátil' y '&' vs "value", pronto se vuelve imposible de traducir. Por supuesto, para "un" caso particular, puede funcionar bastante bien, en general, aunque es difícil. Y, por supuesto, como se indica en el párrafo más grande, si bien ahora es técnicamente factible, podría no ser conveniente por otros motivos. –

+0

He pensado en usar un rasgo de clase para resolver este problema hasta cierto punto (¡aunque nunca lo intenté!). La idea es que cada mixin declare un tipo anidado llamado, digamos, ModelTraits que incluye un T :: ModelTraits como miembro. Tenga en cuenta que esta es una definición recursiva recursiva. El ModelTraits se utiliza para inicializar la clase y los T :: ModelTraits se pueden pasar al constructor de la clase base. –

5

Las plantillas requieren que la implementación sea visible en la unidad de traducción, no solo en el tiempo del enlace (direcciones C++ 11 que si solo usa un puntero o referencia a instancias). Este es un problema importante para el código de bajo nivel en los entornos empresariales: los cambios en la implementación desencadenarán (podrían o no ser automáticamente) números masivos de bibliotecas y clientes para recompilar, en lugar de simplemente volver a vincular.

Además, cada instanciación de plantilla crea un tipo diferenciado, lo que significa que las funciones destinadas a funcionar en cualquiera de las instancias de plantilla deben poder aceptarlas, ya sea que se ven forzadas a ser modeladas o necesitan una forma de transferencia a polimorfismo en tiempo de ejecución (que a menudo es bastante fácil de hacer: solo necesita una clase base abstracta que exprese el conjunto de operaciones admitidas, y alguna función "obtener un descriptor de acceso" que devuelve un objeto derivado con un puntero a la instanciación de la plantilla y entradas relacionadas en el tabla de despacho virtual).

De todos modos, estos problemas son generalmente manejables, pero las técnicas para administrar el acoplamiento, las dependencias y las interfaces involucradas son mucho menos publicitadas, entendidas y fácilmente disponibles que la técnica de mixin simple. Lo mismo es cierto para las plantillas y la clase de política BTW.

+0

Veo el uso de interfaces abstractas como la forma principal de evitar el acoplamiento y por lo tanto las preocupaciones de compilación. Creo que es un buen comentario. Nuestro equipo es relativamente pequeño, pero me pregunto si las bases de código necesariamente tienen que ser tan grandes y estar acopladas. –

+0

Si tuviera que coronar un camino "primario", definitivamente iría con una simple implementación fuera de línea (sin despacho virtual), pero tanto las interfaces pImpl como las abstractas también tienen su lugar. De todos modos, estas cosas se pueden administrar. Otra técnica es tener un front-end sin plantillas para la instanciación de plantilla específica que desee, con la plantilla apoyando directamente la implementación fuera de línea pero no visible a través del encabezado. Muchas opciones para reducir o controlar el acoplamiento, así como opciones para facilitar el movimiento entre el tiempo de ejecución y el polimorfismo en tiempo de compilación. –

Cuestiones relacionadas