2009-07-22 13 views
33

Tengo que tomar una decisión con respecto a la generalización frente al polimorfismo.C++ práctica estándar: clases de interfaz virtual frente a plantillas

Bueno, el escenario es estándar: quiero que mi código interdependiente monolítico sea más modular, limpio y extensible. Todavía está en una etapa donde el principio de cambio de diseño es factible, y, como lo veo, muy deseable.

¿Introduciré clases base (interfaces) o plantillas puramente virtuales?

Soy consciente de los conceptos básicos con respecto a la opción de plantilla: menos indirecto, un mejor rendimiento, más la compilación pero sin fines de unión, y así sucesivamente.

stl no usa mucho (¿o ninguna?) Herencia, y el impulso tampoco. Pero creo que están destinadas a ser herramientas básicas muy pequeñas que se utilizan cada 2 líneas de código por el programador.

considero la herencia y el enfoque de unión tarde para ser más sensata para plug-in de estilo de grandes piezas de código y funcionalidades que deberían ser intercambiables, etc. actualizable después del despliegue o incluso durante el tiempo de ejecución.

Bueno, mi situación se encuentra un poco en el medio.

No necesito intercambiar trozos de código sobre la marcha en tiempo de ejecución, el tiempo de compilación está bien. Por lo general, también es una función muy central y de uso frecuente, que no es lógicamente separable en bloques grandes.

Esto me permite atender algo a la solución de la plantilla. Para mí también se ve algo más limpio.

¿Hay alguna mala implicación, las interfaces siguen siendo EL camino para llevar? ¿Cuándo no lo son? ¿Cuál cumple más con el estilo estándar de C++?

Sé que esto es rayano en subjetivo, pero estoy realmente interesado en algunas experiencias. No tengo una copia de Scott Meyers vigente C++ así que puse mis esperanzas en ustedes :)

Respuesta

20

Básicamente tiene razón, el polimorfismo dinámico (herencia, virtuales) es generalmente la elección correcta cuando el tipo debe ser permitido cambiar en tiempo de ejecución (por ejemplo, en arquitecturas de plugins). El polimorfismo estático (plantillas) es una mejor opción si el tipo solo debe cambiar en tiempo de compilación.

Las únicas desventajas potenciales de las plantillas son que 1) generalmente tienen que definirse en los encabezados (lo que significa que hay más código # incluido), y esto a menudo conduce a tiempos de compilación más lentos.

Pero desde el punto de vista del diseño, no puedo ver ningún problema en el uso de plantillas cuando sea posible.

¿Cuál cumple más con el estilo estándar de C++ ?

Depende de qué es "standard C++ style". La biblioteca estándar de C++ utiliza un poco de todo.El STL usa plantillas para todo, la biblioteca IOStreams un poco más antigua usa herencia y funciones virtuales, y las funciones de biblioteca heredadas de C no usan ninguna, por supuesto.

En estos días, las plantillas son, con mucho, la opción más popular, y tengo que decir que es el enfoque más "estándar".

+3

Bueno, veo un problema al usar plantillas en lugar de interfaces: los requisitos son totalmente implícitos. Cuando tiene que implementar la función virtual pura, se le asigna su firma exacta. Pero cuando ve un tipo de plantilla como _AllocT o Iter, no tiene idea de lo que su clase debe tener ni si incluso tiene que ser una clase. La única forma de saberlo es buscando una documentación decente sobre ella, que tuve problemas para hacer hoy cuando intento crear mi propia clase de asignador compatible con stl. – Virus721

+3

"La única forma de saberlo es buscando una documentación decente sobre ello", o al intentar compilar y ver qué funciones el compilador se queja de no poder encontrar, sí. Además, los Conceptos están destinados a resolver este problema. (E incluso si hubiera sido una interfaz, aún necesitaría encontrar documentación decente.Saber qué funciones anular no es suficiente. También necesita saber cuál debería ser su semántica, y la interfaz no le dice eso). Aún así, tienes razón. Hay una razón por la cual el lenguaje es compatible con ambos. :) – jalf

8

Es una especie de oposición falsa. Sí, el uso principal de la herencia y las funciones virtuales está en iostreams, que son muy antiguas y están escritas en un estilo bastante diferente al resto de las bibliotecas std.

Pero muchas de las bibliotecas C++ modernas más "geniales" como boost usan el polimorfismo en tiempo de ejecución, solo usan plantillas para que sea más cómodo de usar.

boost::any y std::tr1::function (anteriormente también de boost) son buenos ejemplos.

Ambos son contenedores de elementos individuales para algo cuyo tipo concreto se desconoce en tiempo de compilación (esto es especialmente obvio con any, ya que tiene su propio tipo de operador dinámico para obtener el valor).

+0

+1 Tu respuesta fue muy esclarecedora, pero de acuerdo con la pregunta, acepté el consejo de jalf. Gracias. – AndreasT

9

Propiedades de polimorfismo clásico orientado a objetos:

  • objetos están obligados en tiempo de ejecución; esto es más flexible, pero también consume más recursos (CPU) en tiempo de ejecución
  • tipado fuerte trae más seguridad de tipo, pero la necesidad de dynamic_cast (y su potencial para explotar en la cara de un cliente) podría compensar fácilmente eso
  • probablemente más ampliamente conocido y comprendido, pero "clásicos" jerarquías de herencia profundas parece horrible para mí

Propiedades de polimorfismo en tiempo de compilación por plantilla:

  • vinculante tiempo de compilación permite Optimizat más agresivo iones, pero evita la flexibilidad en tiempo de ejecución
  • pato-tipado puede parecer más incómodo, pero las fallas son generalmente fallas en tiempo de compilación
  • a veces puede ser más difícil de leer y comprender; sin conceptos, los diagnósticos del compilador pueden a veces enloquecer

Tenga en cuenta que no es necesario decidirse por ninguno. Puedes mezclar libremente y mezclar ambos (y muchos otros modismos y modismos). A menudo, esto conduce a un código muy impresionante (y expresivo). (Consulte, por ejemplo, cosas como borrado de tipos). Para tener una idea de lo que es posible mediante la combinación inteligente de paradigmas, es posible que desee navegar a través del "Diseño moderno en C++" de Alexandrescu.

+0

Escribir fuerte en OOP? Eso ni siquiera tiene sentido. Obtiene todos los beneficios de seguridad del tipo del polimorfismo en tiempo de compilación. En OOP, escriba borrado (por ejemplo, ocultando el tipo real detrás de una interfaz) y los up/downcasts prácticamente eliminan cualquier esperanza que pudiera haber tenido para el tipo safty. – jalf

+2

Tiene toda la razón. Lo que quise decir, sin embargo, (y redactado mal) es que con una polimorfa en tiempo de ejecución, puedes estar seguro de que lo que obtienes es una implementación deliberada de alguna interfaz, mientras que pato escribiendo polimorfia en tiempo de compilación podría aceptar cualquier coincidencia accidental. ¿Cómo decir esto mejor? – sbi

0

Desde mi punto de vista, es lo que siempre eres mejor. Si tiene más experiencia con OO, use OO. Si tiene más experiencia con los genéricos, use genéricos.

Ambas técnicas tienen algunos patrones equivalentes que significan muchas cosas que puedes usar. Estrategia EG en OO vs Política en genéricos o Método de plantilla en OO vs Patrón de plantilla recurrente en genéricos.

Si está planeando un código de producción de refactor que ya funciona pero la estructura es un poco apestosa.No lo use como excusa para jugar con una nueva técnica de diseño, porque en uno o dos años una vez que comprenda mejor la técnica, puede arrepentirse de cómo refactorizó su código. Es muy fácil introducir nuevas inflexibilidades al aplicar técnicas a medida que las aprende. El objetivo de es mejorar el diseño del código existente si no eres experto en una técnica, ¿cómo sabes que estás mejorando el diseño en lugar de construir un gran símbolo fálico en tu código?

Personalmente soy mejor en OO y tiendo a favorecerlo ya que sé que puedo producir diseños limpios que sean fáciles de entender y que la mayoría de las personas puedan cambiar. La mayoría del código genérico I está destinado a interactuar con otro código genérico, por ejemplo, escribir un iterador o una función genérica para algoritmos.

4

Después de una pala un poco más de experiencia en mi plato, hay algunas cosas en las plantillas que no me gustan: Hay ciertas desventajas que descalifican a la programación meta plantilla de ser un lenguaje utilizable:

  • legibilidad: demasiados corchetes, demasiados forzadas (por lo tanto, mal utilizados) los convenios no lingüísticas
  • para alguien que experimenta la evolución habitual en lenguajes de programación, las plantillas son ilegibles und incomprensible (basta con ver impulso BGL)
  • a veces se siente como si alguien intentó a w rite un generador de código de C++ en awk.
  • mensajes de error del compilador son cl (completo) ed crap
  • demasiados hacks necesarios (la mayoría de ellos se corrigió en C++ 0x) para obtener un cierto "lenguaje básico" como la funcionalidad.
  • No hay plantillas en los archivos de implementación que dan como resultado bibliotecas solo de encabezado (que es una espada de dos caras)
  • usuales Las funciones de finalización de código de IDE no son de mucha ayuda con las plantillas.
  • Hacer cosas grandes en MPL parece "haggly", no puede encontrar otra palabra para ello. Cada línea de código de plantilla produce restricciones en ese tipo de plantilla, que se aplican de una manera que reemplaza el texto. Hay semántica inherente a las jerarquías de herencia, no hay ninguna en las estructuras de las plantillas. Es como si todo fuera un vacío * y el compilador intenta decirle si habrá una segfault.

Dicho todo esto, lo utilizo con bastante éxito en los servicios básicos y las bibliotecas. Escribir funcionalidad de alto nivel o cosas relacionadas con el hardware con él, no parece ser suficiente para mí. Lo que significa que yo planto mis bloques de construcción pero construyo la casa de la manera clásica.

0

Utilizo ambos en mi base de código grande. Cuando se conoce el tipo en tiempo de compilación, lo diseño con plantillas, cuando se lo conoce solo en tiempo de ejecución, utilizo funciones virtuales. Encuentro que las funciones virtuales son más fáciles de programar y de leer más adelante, sin embargo, hay ocasiones en las que el rendimiento es crítico y el hecho de que el polimorfismo con plantilla (si realmente se puede llamar polimorfismo) se puede alinear realmente ayuda.

Cuestiones relacionadas