2009-06-09 17 views
8

Un problema en grandes proyectos de C++ puede ser tiempos de compilación. Hay una clase alta en su árbol de dependencias en la que tendría que trabajar, pero generalmente evita hacerlo porque cada compilación lleva mucho tiempo. No necesariamente quiere cambiar su interfaz pública, pero tal vez quiera cambiar sus miembros privados (agregue una variable de caché, extraiga un método privado, ...). El problema al que se enfrenta es que en C++, incluso los miembros privados se declaran en el archivo de encabezado público, por lo que su sistema de compilación necesita volver a compilar todo.¿Qué patrones usa para desacoplar interfaces e implementación en C++?

¿Qué haces en esta situación?

He esbozado dos soluciones que conozco, pero ambas tienen sus desventajas, y tal vez hay una mejor en la que todavía no he pensado.

Respuesta

9

El patrón pimpl:

En el archivo de cabecera, sólo se declaran los métodos públicos y privados un puntero (el pimpl y agujas, o delegado) a una clase de implementación hacia adelante declarado.

En su fuente, declare la clase de implementación, reenvíe cada método público de su clase pública al delegado y construya una instancia de su clase pimpl en cada constructor de su clase pública.

Plus:

  • Le permite cambiar la implementación de su clase sin tener que recompilar todo.
  • La herencia funciona bien, solo la sintaxis se vuelve un poco diferente.

Menos:

  • Montones y montones de cuerpos de métodos estúpidos a escribir para hacer la delegación.
  • Es un poco incómodo de depurar ya que tiene un montón de delegados para pasar.
  • Puntero adicional en su clase base, lo que podría ser un problema si tiene muchos objetos pequeños.
2

de la herencia:

En su cabecera, declaran los métodos públicos como los métodos virtuales puros y una fábrica.

En su fuente, obtenga una clase de implementación de su interfaz e impleméntela. En la implementación de la fábrica devuelve una instancia de la implementación.

Plus:

  • Le permite cambiar la implementación de su clase sin tener que recompilar todo.
  • Fácil y infalible de implementar.

Menos:

  • realmente difícil de definir un (público) instancia derivada de la clase base pública que debe heredar algunos de los métodos de la aplicación (privada) de la base pública.
+0

Otra ventaja es que puede simular fácilmente la clase para la prueba unitaria si tiene una interfaz –

0

Puede usar una declaración directa para la clase A a la que se hace referencia mediante puntero en otra clase B. Luego puede incluir el archivo de encabezado A de clase en el archivo de implementación de la clase B en lugar de su archivo de encabezado. De esta forma, los cambios que realice en la clase A no afectarán a los archivos fuente que incluyen el archivo de encabezado de la clase B. Cualquier clase que quiera acceder a los miembros de la clase A deberá incluir el archivo de encabezado de la clase A.

+0

Esto es correcto, y una buena práctica en cualquier caso. Sin embargo, a veces no parece suficiente (cuando tienes muchos clientes que necesitan usar la clase). – Tobias

6

John Lakos 'Large Scale C++ Software Design es un excelente libro que aborda los desafíos involucrados en la construcción de grandes proyectos de C++. Los problemas y las soluciones están todos basados ​​en la realidad, y ciertamente el problema anterior se discute en detalle. Muy recomendable.

+0

De acuerdo, un clásico, aunque un poco lento. Por cierto, escuché que iba a lanzar una segunda edición. ¿Alguien escuchó una actualización sobre esto? +1 –

+0

Drew - ¿Recuerdas dónde oíste esto (alrededor de una segunda edición)? Además, había visto que Lakos iba a tener un libro en 2006 sobre Scalable C++, pero parece haber nacido muerto (o al menos retrocedido). Es una pena porque el libro (LSCSD) realmente aborda una necesidad, Esperaba que después de 13 años se publicara una actualización. – Dan

+0

Dan - Desearía poder recordarlo - Pensé que estaba en el sitio web de AW pero ya no lo veo. Tal vez incluso Amazon? Tal vez solo ilusiones de mi parte ... –

0

La refactoreo y uso del idioma pimpl/handle-body, el uso de interfaces virtuales puras para ocultar los detalles de implementación parece ser la respuesta popular. Uno debe considerar el tiempo de compilación y la productividad del desarrollador al diseñar sistemas grandes. ¿Pero qué ocurre si está trabajando en un gran sistema C++ existente sin cobertura de prueba unitaria? La refactorización generalmente está fuera de discusión.

Lo que suelo hacer cuando no quiero que el compilador compile el mundo después de haber tocado algunos archivos de encabezado comunes es tener un makefile/script para compilar solo los archivos que sé necesitan recompilar. Por ejemplo, si estoy agregando una función privada no virtual a una clase, solo el archivo cpp de la clase necesita ser recompilado, incluso cuando su archivo de encabezado está incluido en un centenar de otros archivos. Antes de irme por el día, comienzo una construcción limpia para reconstruir el mundo.

+0

¿Tiene un objetivo genérico en su archivo MAKE o compila y vincula manualmente? – Tobias

0

Ninguno.

veo el punto en el uso de uno, pero creo que los siguientes argumentos mitigar ese punto en muchos escenarios:

  1. La claridad es lo primero. Si se pone en peligro la claridad del tiempo de ejecución , la velocidad debe considerarse dos veces, ¿qué acerca de la claridad comprometedora para tiempo de compilación velocidad?
  2. Los miembros privados no deberían cambiar tan a menudo.
  3. Por lo general, no toma que largo para reconstruir todo.
  4. Herramientas más rápidas vendrán en el futuro, por lo que el problema de compilación de velocidad se mitigará automáticamente. Tu código no se aclarará automáticamente.
  5. Debe reconstruir con frecuencia de todos modos.
  6. ¿Has probado Incredibuild?

Por supuesto que al final esta es una decisión económica. Si el peso de "3" es importante en su proyecto y por alguna razón "6" no puede aplicarse, entonces continúe: ganará más usando estas plantillas de lo que pierde.

+1

Su punto es ciertamente válido, este tipo de cosas no deben hacerse prematuramente, ya que no debe optimizar el código antes del perfilado. No estoy de acuerdo con el punto 2: el software no es estático, sino que está en constante evolución. En todo caso, debes tratar de mantener constante tu interfaz pública, pero incluso eso cambia rápidamente cuando refaccionas las cosas. También estoy en desacuerdo con 4. Aparecerán herramientas más rápidas, pero mientras tanto, los desarrolladores habrán agregado toneladas de loc. El punto 5 es absolutamente correcto, y es parte de la razón por la que desea utilizar dicho patrón. – Tobias

Cuestiones relacionadas