¿Podría C++ no haber obviado el modismo pimpl?
language-design compiler-theory (6)
Según tengo entendido, el modismo pimpl solo existe porque C ++ te obliga a colocar todos los miembros privados de la clase en el encabezado. Si el encabezado fuera a contener solo la interfaz pública, teóricamente, cualquier cambio en la implementación de la clase no habría requerido una recompilación para el resto del programa.
Lo que quiero saber es por qué C ++ no está diseñado para permitir tal conveniencia. ¿Por qué exige que las partes privadas de una clase se muestren abiertamente en el encabezado (sin juego de palabras)?
¿Puede ser porque se requiere el tamaño de la clase al pasar su instancia por valores, agregarla en otras clases, etc.?
Si C ++ no era compatible con la semántica de valores, hubiera estado bien, pero lo hace.
Alguien tendrá una respuesta mucho más detallada que yo, pero la respuesta rápida es doble: el compilador necesita conocer a todos los miembros de una estructura para determinar los requisitos de espacio de almacenamiento, y el compilador necesita saber el orden de esos miembros para generar compensaciones de forma determinista.
El lenguaje ya es bastante complicado; Creo que un mecanismo para dividir las definiciones de datos estructurados a través del código sería un poco una calamidad.
Por lo general, siempre he visto clases de políticas usadas para definir el comportamiento de implementación de una manera Pimpl. Creo que hay algunos beneficios adicionales de utilizar un patrón de política: más fácil de intercambiar implementaciones, puede combinar fácilmente múltiples implementaciones parciales en una sola unidad que le permite dividir el código de implementación en unidades funcionales, reutilizables, etc.
Creo que hay una confusión aquí. El problema no es sobre encabezados. Los encabezados no hacen nada (son solo formas de incluir bits comunes de texto fuente entre varios archivos de código fuente).
El problema, tanto como hay uno, es que las declaraciones de clase en C ++ tienen que definir todo, público y privado, que una instancia necesita tener para funcionar. (Lo mismo es cierto para Java, pero la forma en que funciona la referencia a clases compiladas externamente hace innecesario el uso de cualquier cosa, como los encabezados compartidos).
Está en la naturaleza de las tecnologías comunes orientadas a objetos (no solo la de C ++) que alguien necesita saber la clase concreta que se usa y cómo usar su constructor para entregar una implementación, incluso si está utilizando solo las partes públicas. El dispositivo en (3, abajo) lo oculta. La práctica en (1, abajo) separa las preocupaciones, ya sea que lo haga (3) o no.
Utilice clases abstractas que definan solo las partes públicas, principalmente los métodos, y deje que la clase de implementación herede de esa clase abstracta. Entonces, usando la convención usual para los encabezados, hay un abstract.hpp que se comparte. También hay una implementation.hpp que declara la clase heredada y que solo se pasa a los módulos que implementan los métodos de la implementación. El archivo implementation.hpp incluirá # "abstract.hpp" para usar en la declaración de clase que realiza, de modo que haya un único punto de mantenimiento para la declaración de la interfaz abstraída.
Ahora, si desea aplicar el ocultamiento de la declaración de clase de implementación, debe tener alguna forma de solicitar la construcción de una instancia concreta sin poseer la declaración de clase completa y específica: no puede usar las nuevas y no puede usar instancias locales . (Sin embargo, puede eliminar.) La introducción de funciones auxiliares (incluidos los métodos en otras clases que entregan referencias a instancias de clase) es el sustituto.
Junto con o como parte del archivo de encabezado que se utiliza como la definición compartida para la clase / interfaz abstracta, incluya firmas de función para funciones auxiliares externas. Esta función debe implementarse en módulos que forman parte de las implementaciones de clases específicas (para que vean la declaración de clase completa y puedan ejercitar el constructor). La firma de la función auxiliar es probablemente muy similar a la del constructor, pero devuelve una referencia de instancia como resultado (Este proxy constructor puede devolver un puntero NULL e incluso puede lanzar excepciones si le gusta ese tipo de cosas). La función auxiliar construye una instancia de implementación particular y la devuelve fundida como referencia a una instancia de la clase abstracta.
Misión cumplida.
Ah, y la recompilación y el reenlazado deberían funcionar de la manera que desee, evitando la recompilación de los módulos de llamada cuando solo cambia la implementación (dado que el módulo que llama ya no realiza asignaciones de almacenamiento para las implementaciones).
Sí, pero...
Necesita leer el libro "Diseño y evolución de C ++" de Stroustrup. Habría inhibido la absorción de C ++.
Todos ignoran el objetivo de la pregunta:
¿Por qué el desarrollador debe escribir el código PIMPL?
Para mí, la mejor respuesta que puedo encontrar es que no tenemos una buena manera de expresar el código C ++ que le permite operar en él. Por ejemplo, reflejo en tiempo de compilación (o preprocesador, o lo que sea) o un código DOM.
C ++ necesita urgentemente que uno o ambos estén disponibles para que un desarrollador haga meta-programación.
Entonces podrías escribir algo como esto en tu MyClass.h público:
#pragma pimpl(MyClass_private.hpp)
Y luego escribe tu propio generador de envoltura realmente bastante trivial.
Esto tiene que ver con el tamaño del objeto. El archivo h se utiliza, entre otras cosas, para determinar el tamaño del objeto. Si los miembros privados no figuran en él, entonces no sabría qué tan grande es un objeto nuevo.
Sin embargo, puede simular su comportamiento deseado de la siguiente manera:
class MyClass
{
public:
// public stuff
private:
#include "MyClassPrivate.h"
};
Esto no hace cumplir el comportamiento, pero saca las cosas privadas del archivo .h. En el lado negativo, esto agrega otro archivo para mantener. Además, en Visual Studio, el intellisense no funciona para los miembros privados, esto podría ser un plus o un menos.