2008-11-06 6 views
19

Según tengo entendido, el idioma pimpl existe solo 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.¿Pudo C++ no haber obviado el modismo pimpl?

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)?

+0

@Frederick, no eres un "bobo", ¡es una buena pregunta! – jwfearn

+0

Bien manchado (juego de palabras)! –

Respuesta

10

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.

Se puede simular, sin embargo, su comportamiento deseado por el siguiente texto:

class MyClass 
{ 
public: 
    // public stuff 

private: 
#include "MyClassPrivate.h" 
}; 

Esto no hace cumplir el comportamiento, pero hace el cosas privadas fuera 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 punto a favor o un punto negativo.

+0

¡Pero por supuesto! Qué bobo soy para hacer esa pregunta. Gracias a todos de todos modos. –

+0

Otro inconveniente es que un cambio en la interfaz privada aún requiere una recompilación de los clientes. – peterchen

+4

¿Por qué se acepta esta respuesta? "MyClassPrivate.h" se puede leer tan fácilmente como el encabezado original. Todavía requiere la recompilación. El tamaño del objeto es un problema menor. Los verdaderos stop-shows son la eficiencia y la compatibilidad con algunos modismos en C. – jfs

3

¿Puede ser porque el tamaño de la clase es necesario 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.

+0

No es que C++ sea compatible con la semántica de valores ... Es que C++ realmente analizará por el valor para proporcionar esa semántica (que es el enfoque más eficiente y más simple, de todos modos). – Arafangion

+0

@Ara: ¿Podría explicar el término "analizar por valor"? Nunca escuché sobre eso. – fredoverflow

+0

@Fred: Ups, has atrapado un error tipográfico mío: lo hago, de hecho, significa pase.:) – Arafangion

5

Alguien tendrá una respuesta mucho más detallada que yo, pero la respuesta rápida es doble: el compilador necesita conocer 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 una manera 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 policy classes utilizado para definir el comportamiento de implementación de una manera Pimpl. Creo que hay algunos beneficios adicionales de usar un patrón de política: más fácil de intercambiar implementaciones, puede combinar múltiples implementaciones parciales en una sola unidad que le permite dividir el código de implementación en unidades funcionales y reutilizables, etc.

3

Sí, pero ...

Es necesario leer el libro "Evolución de C++ y Diseño" de BS. Habría inhibido la absorción de C++.

6

Eres todo ignorando el punto de la pregunta -

Por qué debe escribir el desarrollador 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.

Luego podría escribir algo como esto en su MyClass pública.h:

#pragma pimpl(MyClass_private.hpp) 

Y a continuación, escriba su propio generador de envoltura realmente bastante trivial.

9

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 poder funcionar. (Lo mismo es cierto para Java, pero la forma en que funciona la referencia a clases compiladas externamente hace que el uso de cualquier encabezado compartido sea innecesario.)

Está en la naturaleza de las Tecnologías Orientado a Objetos comunes (no solo en 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.

  1. Utilice clases abstractas que solo definan 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.

  2. Ahora, si desea hacer cumplir la ocultación de la declaración de clase de implementación, necesita 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 nueva y puede No use 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.

  3. Junto con o como parte del archivo de encabezado que se utiliza como la definición compartida para la clase/interfaz abstracta, incluya las firmas de función para las 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 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 (ya que el módulo que llama ya no hace asignaciones de almacenamiento para las implementaciones).

+0

Eso no parece una buena idea. Es hacky y parece que estás luchando activamente contra el idioma en lugar de usarlo. De repente, los punteros opacos en C parecen una solución limpia y simple ... – dietr

Cuestiones relacionadas