c++ forward-declaration

c++ - Gestionando declaraciones a plazo.



forward-declaration (9)

Es bien sabido que usar declaraciones a plazo es preferible a usar #includes en archivos de encabezado, pero ¿cuál es la mejor manera de administrar las declaraciones en adelante?

Durante un tiempo, agregué manualmente a cada archivo de encabezado las declaraciones de reenvío que necesitaba ese archivo de encabezado. Sin embargo, terminé con un montón de archivos de encabezado que repetían la misma media docena de declaraciones en adelante, lo que parece redundante, y mantener estas listas repetidas llegó a ser un poco tedioso.

Las declaraciones adelantadas de typedefs (por ejemplo, struct SensorRecordId; typedef std::vector<SensorRecordId> SensorRecordIdList; ) también es un poco más para duplicar en varios archivos de encabezado.

Entonces, hice un archivo ProjectForwards.h que contiene todas mis declaraciones de reenvío y lo incluí donde sea necesario. Al principio, parecía una buena idea, y mucho menos redundancia, y un mantenimiento mucho más sencillo de los typedefs. Pero ahora, como resultado de usar ProjectForwards.h tan fuertemente, cada vez que le agrego una nueva clase, tengo que reconstruir el mundo, lo que ralentiza el desarrollo.

Entonces, ¿cuál es la mejor manera de gestionar las declaraciones a futuro? ¿Debo morder la bala y repetir declaraciones directas individuales a través de múltiples subsistemas? Continuar con el enfoque de ProjectForwards.h ? ¿Intenta dividir ProjectForwards.h en varios archivos SubsystemForwards.h ? ¿Alguna otra solución que estoy pasando por alto?


Estaba agregando manualmente a cada archivo de encabezado las declaraciones de reenvío que necesitaba ese archivo de encabezado.

Esta es la única manera buena.

Además, si tiene un typedef en algún lugar, es mejor enmascararlo de alguna manera. Por ejemplo, en lugar de usar un typedef como este:

typedef std::vector< MyClass > MyClassArray;

haz esto en su lugar:

struct MyClassArray { std::vector< MyClass > t; };

Lo malo es que no podrá utilizar operadores, por lo que esto no siempre funcionará. Por ejemplo, si tiene

typedef std::string MyString;

Entonces es mejor ir con typedef.

Entonces, hice un archivo ProjectForwards.h que contiene todas mis declaraciones de reenvío y lo incluí donde sea necesario.

Como has descubierto, esta es una muy mala idea. Cuando modifique este encabezado, activará la recompilación de todos los archivos que lo incluyan (directa o indirectamente).


Después de haber realizado muchas tareas de mantenimiento de campo marrón, nunca me ha gustado incluir artículos que no hacen más que incluir otros archivos o tener declaraciones hacia adelante. Prefiero tenerlos solo en el archivo de cabecera. Puede reducir la escritura con el uso de plantillas si sus herramientas las admiten. Podría escribir una plantilla que se expande en el texto deseado. Probablemente incluiría algo para que se destaque como

///Begin Forwarding ... ///End Forwarding

Eso haría que sea fácil de agarrar y reemplazar si cambia la plantilla. Si se siente más cómodo con herramientas como grep, puede automatizar la actualización desde una línea de comandos. Probablemente sería sencillo escribir un script que actualice todos los archivos, o solo los archivos pasados ​​en la línea de comandos. Solo un pensamiento.


En general:

  1. Tenga un archivo de reenvío para los usuarios de su módulo. Esto solo declarará aquellas clases que aparecen como parte de la API.

  2. Si ha utilizado los reenvíos en su implementación, puede tener un archivo de reenvíos basado solo en la implementación.

  3. Probablemente no necesite una declaración de reenvío para cada clase que use.


Esto es lo que generalmente hago:

Es bien sabido que usar declaraciones a plazo es preferible a usar #includes en archivos de encabezado, pero ¿cuál es la mejor manera de administrar las declaraciones en adelante?

  • Biblioteca: Proporcione un reenvío de cliente dedicado: (por ejemplo, #include "MONThread/include.fwd.hpp" ). Mantenga las bibliotecas enfocadas (small-ish) y haga las implementaciones privadas cuando sea posible.

  • Ejecutable: la declaración de reenvío a petición, a menos que provenga de una biblioteca, siempre use la inclusión de la biblioteca en adelante. Reconozca lo que debería ser una biblioteca (lógica o física); muchos usuarios avanzados sugieren esto, ya que surgirán patrones. También trate de aislar lo que se puede ocultar en el proceso. Con las bibliotecas y los ejecutables, debe haber algún tipo de uso de paquetes privados, estos tipos no pertenecen a los encabezados directos del cliente.

Entonces, hice un archivo ProjectForwards.h que contiene todas mis declaraciones de reenvío y lo incluí donde sea necesario. Al principio, parecía una buena idea, y mucho menos redundancia, y un mantenimiento mucho más sencillo de los typedefs. Pero ahora, como resultado de usar ProjectForwards.h tan fuertemente, cada vez que le agrego una nueva clase, tengo que reconstruir el mundo, lo que ralentiza el desarrollo.

Por lo general, eso significa que muchas bibliotecas grandes son visibles en niveles altos del gráfico de inclusión. Un gráfico de inclusión ideal (de un sistema grande) es mucho más ancho que alto, incluyendo lo que necesita con un mínimo de exceso. Si cada TU necesita unas 100.000 líneas, está más allá de un problema: comience a eliminar bibliotecas grandes de niveles altos.

Si eso realmente suena insatisfactorio, analice las dependencias de su programa.

  • Muchas personas cometen el error (en proyectos más grandes) de incluir una tonelada de bibliotecas grandes por conveniencia (por ejemplo, en el pch), lo que resulta en la recompilación del mundo (y del pch).

  • Evalúe sus dependencias de vez en cuando: establezca algunos límites sensibles para el recuento de líneas de salida del preprocesador.

  • Los encabezados de avance reemplazan las declaraciones de avance locales. Ellos no (generalmente) pertenecen en el pch.


No creo que exista una única "mejor" solución, cada una tiene sus propias ventajas e inconvenientes. A pesar de que es más trabajo, personalmente estoy a favor del enfoque de "cada archivo de encabezado tiene sus propias declaraciones hacia adelante", por las siguientes razones:

  • Es lo más simple posible: no es necesario encontrar y analizar archivos adicionales.
  • Sin ofuscación: solo mirando el archivo de encabezado verá exactamente qué tipos necesita.
  • No hay contaminación innecesaria del espacio de nombres. Si recopila declaraciones en un archivo ProjectForwards.h , ese archivo contendrá la suma de todas las declaraciones que necesitan todos sus consumidores. Por lo tanto, si solo un consumidor necesita una determinada declaración, todos los demás también la heredarán.

Si estos argumentos no son convincentes, tal vez porque son demasiado puristas :-), entonces sugeriría seguir la forma intermedia de dividir ProjectForwards.h .


No hay ninguna declaración de escape hacia delante donde se necesiten.
En su modelo, si cada uno de sus objetos de un tipo se comunica con otros objetos de otro tipo usando solo las interfaces, entonces minimizará la cantidad de declaración directa a las interfaces solamente.
Si usa plantillas, puede colocar sus definiciones de tipo en el archivo de encabezado precompilado.


Nunca he visto un "encabezado de declaraciones futuras" que fue realmente útil (nadie lo usa), no se convirtió rápidamente en obsoleto (lleno de cosas que nadie usa) y no fue un cuello de botella de iteración (tocó el encabezado de declaración directa) ? recompilar todo!). Generalmente desarrollan los tres problemas.

El núcleo de su problema es el diseño del sistema. Estos subsistemas que ha mencionado probablemente deberían incluir los archivos de encabezado que definen los tipos que deben tomar como entrada o salida. Al dividir los tipos que están utilizando varios subsistemas en su propio archivo de encabezado, logrará un buen equilibrio entre el aislamiento y la interoperabilidad eficiente entre los subsistemas.


Parece que estas clases son bastante comunes a gran parte de su proyecto. Puede probar algunos de estos:

  • Haz tu mejor ProjectForwards.h para dividir ProjectForwards.h en varios archivos como sugeriste. Asegúrese de que cada subsistema solo obtenga las declaraciones que realmente necesita. Si no es otra cosa, ese proceso lo obligará a pensar en el acoplamiento entre sus subsistemas y podría encontrar formas de reducirlo. Todos estos son buenos pasos para evitar el exceso de compilación.

  • Mímico <iosfwd> . Haga que cada clase o módulo común proporcione su propio encabezado de inclusión hacia adelante que solo proporcione los nombres de clase y cualquier tipo de definición de tipo conveniente. Entonces puedes #incluir eso en todas partes. Sí, repetirás mucho la lista, pero piénsalo de esta manera: nadie se queja sobre #incluyendo <vector> , <string> y <map> en seis lugares diferentes de su código.

  • Utilice Pimpl más a menudo. Esto tendrá un efecto similar a mi sugerencia anterior, pero requerirá más trabajo de su parte. Si sus interfaces son estables, entonces puede proporcionar de forma segura los typedefs en esos encabezados y #incluirlos directamente.


Personalmente solo ProjectForwards.h en el Global ProjectForwards.h las declaraciones que son verdaderamente globales para todos, o en su mayoría, para el programa. También podría incluir otros archivos que casi siempre son necesarios, por ejemplo:

#include <string> #include <vector> #include <boost/shared_ptr.hpp> std::string get_installation_dir(); //...

De esta manera, este archivo rara vez cambia y no es necesario reconstruirlo con frecuencia.

Además, si este archivo incluye un montón de encabezados estándar, sería un candidato perfecto para ser un encabezado precompilado.