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.
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.
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.
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).
@Frederick, no eres un "bobo", ¡es una buena pregunta! – jwfearn
Bien manchado (juego de palabras)! –