Me preguntaba qué haría que un programador eligiera el idioma de Pimpl o la clase virtual pura y la herencia.
Entiendo que el idioma pimpl viene con una indirección adicional explícita para cada método público y la sobrecarga de creación de objetos.
La clase virtual pura, por otro lado, viene con indirección implícita (vtable) para la implementación heredada y entiendo que no hay gastos generales de creación de objetos.
EDITAR : Pero necesitaría una fábrica si crea el objeto desde el exterior
¿Qué hace que la clase virtual pura sea menos deseable que el idioma pimpl?
c++
abstract-class
pimpl-idiom
Arkaitz Jiménez
fuente
fuente
Respuestas:
Al escribir una clase de C ++, es apropiado pensar si va a ser
Un tipo de valor
Copia por valor, la identidad nunca es importante. Es apropiado que sea una clave en un std :: map. Por ejemplo, una clase de "cadena", una clase de "fecha" o una clase de "número complejo". "Copiar" instancias de tal clase tiene sentido.
Un tipo de entidad
La identidad es importante. Siempre se pasa por referencia, nunca por "valor". A menudo, no tiene sentido "copiar" instancias de la clase en absoluto. Cuando tiene sentido, un método de "clonación" polimórfica suele ser más apropiado. Ejemplos: una clase de Socket, una clase de base de datos, una clase de "política", cualquier cosa que sea un "cierre" en un lenguaje funcional.
Tanto pImpl como la clase base abstracta pura son técnicas para reducir las dependencias del tiempo de compilación.
Sin embargo, solo uso pImpl para implementar tipos de valor (tipo 1), y solo a veces cuando realmente quiero minimizar el acoplamiento y las dependencias en tiempo de compilación. A menudo, no vale la pena molestarse. Como señala correctamente, hay más sobrecarga sintáctica porque tiene que escribir métodos de reenvío para todos los métodos públicos. Para las clases de tipo 2, siempre uso una clase base abstracta pura con métodos de fábrica asociados.
fuente
Pointer to implementation
generalmente se trata de ocultar detalles de implementación estructural.Interfaces
se trata de crear instancias de diferentes implementaciones. Realmente sirven para dos propósitos diferentes.fuente
El idioma pimpl le ayuda a reducir las dependencias y los tiempos de compilación, especialmente en aplicaciones grandes, y minimiza la exposición del encabezado de los detalles de implementación de su clase a una unidad de compilación. Los usuarios de su clase ni siquiera deberían tener que ser conscientes de la existencia de una espinilla (¡excepto como un puntero críptico del que no están al tanto!).
Las clases abstractas (virtuales puros) es algo que sus clientes deben tener en cuenta: si intenta usarlas para reducir el acoplamiento y las referencias circulares, debe agregar alguna forma de permitirles crear sus objetos (por ejemplo, a través de métodos o clases de fábrica, inyección de dependencia u otros mecanismos).
fuente
Estaba buscando una respuesta para la misma pregunta. Después de leer algunos artículos y practicar , prefiero usar "interfaces de clase virtual pura" .
El único inconveniente ( estoy tratando de investigar sobre esto ) es que el idioma de pimpl podría ser más rápido
fuente
¡Odio las espinillas! Hacen la clase fea y no legible. Todos los métodos se redirigen al grano. Nunca ves en los encabezados, qué funcionalidades tiene la clase, por lo que no puedes refactorizarla (ej. Simplemente cambiar la visibilidad de un método). La clase se siente como "embarazada". Creo que usar iterfaces es mejor y realmente suficiente para ocultar la implementación al cliente. Puede dejar que una clase implemente varias interfaces para mantenerlas delgadas. ¡Uno debería preferir las interfaces! Nota: No necesita la clase de fábrica. Es relevante que los clientes de la clase se comuniquen con sus instancias a través de la interfaz adecuada. La ocultación de métodos privados me parece una extraña paranoia y no veo razón para esto ya que tenemos interfaces.
fuente
Hay un problema muy real con las bibliotecas compartidas que el idioma pimpl elude perfectamente y que los virtuales puros no pueden: no se pueden modificar / eliminar de forma segura los miembros de datos de una clase sin obligar a los usuarios de la clase a recompilar su código. Eso puede ser aceptable en algunas circunstancias, pero no, por ejemplo, para las bibliotecas del sistema.
Para explicar el problema en detalle, considere el siguiente código en su biblioteca / encabezado compartido:
El compilador emite código en la biblioteca compartida que calcula la dirección del entero que se inicializará para que sea un cierto desplazamiento (probablemente cero en este caso, porque es el único miembro) del puntero al objeto A que sabe que es
this
.En el lado del usuario del código,
new A
primero asignarásizeof(A)
bytes de memoria, luego pasará un puntero a esa memoria alA::A()
constructor comothis
.Si en una revisión posterior de su biblioteca decide eliminar el número entero, hacerlo más grande, más pequeño o agregar miembros, habrá una discrepancia entre la cantidad de memoria que el código del usuario asigna y las compensaciones que espera el código del constructor. El resultado probable es un bloqueo, si tiene suerte; si tiene menos suerte, su software se comporta de manera extraña.
Al hacer pimpl'ing, puede agregar y eliminar miembros de datos de forma segura a la clase interna, ya que la asignación de memoria y la llamada al constructor ocurren en la biblioteca compartida:
Todo lo que necesita hacer ahora es mantener su interfaz pública libre de miembros de datos que no sean el puntero al objeto de implementación, y está a salvo de esta clase de errores.
Editar: tal vez debería agregar que la única razón por la que estoy hablando del constructor aquí es que no quería proporcionar más código; la misma argumentación se aplica a todas las funciones que acceden a los miembros de datos.
fuente
class A_impl *impl_;
No debemos olvidar que la herencia es un vínculo más fuerte y más estrecho que la delegación. También tomaría en cuenta todas las cuestiones planteadas en las respuestas dadas al decidir qué modismos de diseño emplear para resolver un problema en particular.
fuente
Aunque está ampliamente cubierto en las otras respuestas, tal vez pueda ser un poco más explícito sobre un beneficio de pimpl sobre las clases base virtuales:
Un enfoque de pimpl es transparente desde el punto de vista del usuario, lo que significa que, por ejemplo, puede crear objetos de la clase en la pila y usarlos directamente en contenedores. Si intenta ocultar la implementación utilizando una clase base virtual abstracta, deberá devolver un puntero compartido a la clase base desde una fábrica, lo que complica su uso. Considere el siguiente código de cliente equivalente:
fuente
A mi entender, estas dos cosas tienen propósitos completamente diferentes. El propósito del modismo de la espinilla es básicamente darle una idea de su implementación para que pueda hacer cosas como intercambios rápidos por una especie.
El propósito de las clases virtuales está más en la línea de permitir el polimorfismo, es decir, tiene un puntero desconocido a un objeto de un tipo derivado y cuando llama a la función x siempre obtiene la función correcta para cualquier clase a la que el puntero base realmente apunte.
Realmente manzanas y naranjas.
fuente
El problema más molesto del idioma pimpl es que hace extremadamente difícil mantener y analizar el código existente. Por lo tanto, al usar pimpl, paga con el tiempo y la frustración del desarrollador solo para "reducir las dependencias y los tiempos de compilación y minimizar la exposición del encabezado de los detalles de implementación". Decide tú mismo, si realmente vale la pena.
Especialmente los "tiempos de compilación" son un problema que puede resolver mejorando el hardware o utilizando herramientas como Incredibuild (www.incredibuild.com, también ya incluida en Visual Studio 2017), sin afectar así el diseño de su software. El diseño del software debe ser generalmente independiente de la forma en que se construye el software.
fuente