Asegurarse de que los encabezados se incluyan explícitamente en el archivo CPP

9

Creo que en general es una buena práctica #includeel encabezado para cualquier tipo utilizado en un archivo CPP, independientemente de lo que ya se incluye a través del archivo HPP. Por lo tanto, podría #include <string>hacerlo en mi HPP y CPP, por ejemplo, a pesar de que aún podría compilar si me saltaba en el CPP. De esta manera, no tengo que preocuparme de si mi HPP usó una declaración directa o no.

¿Hay alguna herramienta que pueda imponer este #includeestilo de codificación? ¿ Debo aplicar este estilo de codificación?

Dado que al preprocesador / compilador no le importa si #includeproviene de HPP o CPP, no recibo comentarios si me olvido de seguir este estilo.

M. Dudley
fuente

Respuestas:

7

Este es uno de esos "debería" en lugar de "debe" tipos de estándares de codificación. La razón es que tendría que escribir un analizador C ++ para aplicarlo.

Una regla muy común para los archivos de encabezado es que deben estar solos. Un archivo de encabezado no debe requerir que algunos otros archivos de encabezado estén #incluidos antes de incluir el encabezado en cuestión. Este es un requisito comprobable. Dado un encabezado aleatorio foo.hh, se debe compilar y ejecutar lo siguiente:

#include "foo.hh"
int main () {
   return 0;
}

Esta regla tiene consecuencias con respecto al uso de otras clases en algún encabezado. A veces, esas consecuencias pueden evitarse declarando hacia adelante esas otras clases. Esto no es posible con muchas de las clases de biblioteca estándar. No hay forma de reenviar declarar una instancia de plantilla como std::stringo std::vector<SomeType>. Tienes que #includeesos encabezados STL en la cabecera incluso si el único uso del tipo es como un argumento a una función.

Otro problema es con cosas que incidentalmente arrastra. Ejemplo: considere lo siguiente:

archivo foo.cc:

#include "foo.hh"
#include "bar.hh"

void Foo::Foo () : bar() { /* body elided */ }

void Foo::do_something (int item) {
   ...
   bar.add_item (item);
   ...
}

Aquí barhay un Foomiembro de datos de clase que es de tipo Bar. Has hecho lo correcto aquí y has #incluido bar.hh aunque eso debería haberse incluido en el encabezado que define la clase Foo. Sin embargo, no ha incluido las cosas utilizadas por Bar::Bar()y Bar::add_item(int). Hay muchos casos en los que estas llamadas pueden generar referencias externas adicionales.

Si analiza foo.ocon una herramienta como nm, parecerá que las funciones foo.ccestán llamando a todo tipo de cosas para las que no ha hecho lo apropiado #include. Entonces, ¿debería agregar #includedirectivas para esas referencias externas incidentales foo.cc? La respuesta es absolutamente no. El problema es que es muy difícil distinguir aquellas funciones que se llaman incidentalmente de las que se llaman directamente.

David Hammen
fuente
2

¿Debo aplicar este estilo de codificación?

Probablemente no. Mi regla es esta: la inclusión del archivo de encabezado no puede depender del orden.

Puede verificar esto con bastante facilidad con la simple regla de que el primer archivo incluido por xc es xh

Kevin Cline
fuente
1
¿Puedes dar más detalles sobre esto? No puedo ver cómo eso realmente verificaría la independencia del orden.
desvío
1
en realidad no garantiza la independencia del orden, solo garantiza que #include "x.h"funcionará sin requerir algo de precedente #include. Eso es lo suficientemente bueno si no abusas #define.
Kevin Cline
1
Oh ya veo. Sigue siendo una buena idea entonces.
desvío
2

Si necesita hacer cumplir una regla de que los archivos de encabezado en particular deben ser independientes, puede usar las herramientas que ya tiene. Cree un archivo MAKE básico que compile cada archivo de encabezado individualmente pero no genere un archivo objeto. Podrá especificar en qué modo compilar el archivo de encabezado (modo C o C ++) y verificar que puede mantenerse por sí mismo. Puede suponer razonablemente que la salida no contiene ningún falso positivo, se declaran todas las dependencias requeridas y la salida es precisa.

Si está utilizando un IDE, aún puede lograrlo sin un archivo MAKE (dependiendo de su IDE). Simplemente cree un proyecto adicional, agregue los archivos de encabezado que desea verificar y cambie la configuración para compilarlo como un archivo C o C ++. En MSVC, por ejemplo, cambiaría la configuración de "Tipo de elemento" en "Propiedades de configuración-> General".

Capitán Obvio
fuente
1

No creo que exista tal herramienta, pero sería feliz si alguna otra respuesta me refutara.

El problema con escribir una herramienta de este tipo es que informa muy fácilmente un resultado falso, por lo que calculo que la ventaja neta de dicha herramienta es cercana a cero.

La única forma en que una herramienta de este tipo podría funcionar es si pudiera restablecer su tabla de símbolos solo al contenido del archivo de encabezado que procesó, pero luego se encuentra con el problema de que los encabezados que forman la API externa de una biblioteca delegan las declaraciones reales a encabezados internos.
Por ejemplo, <string>en la implementación de GCC libc ++ no se declara nada, pero solo incluye un montón de encabezados internos que contienen las declaraciones reales. Si la herramienta restablece su tabla de símbolos solo a lo que fue declarado por <string>sí mismo, eso no sería nada.
Podría hacer que la herramienta diferencie entre #include ""y #include <>, pero eso no lo ayuda si una biblioteca externa usa #include ""para incluir sus encabezados internos en la API.

Bart van Ingen Schenau
fuente
1

no #Pragma oncelogra esto? Puede incluir algo tantas veces como lo desee, ya sea directamente o mediante encadenamientos incluidos, y siempre que haya #Pragma onceuno al lado de cada uno de ellos, el encabezado solo se incluye una vez.

En cuanto a su cumplimiento, bueno, tal vez podría crear un sistema de compilación que solo incluya cada encabezado por sí mismo con alguna función principal ficticia, solo para asegurarse de que se compile. #ifdefLa cadena incluye para obtener los mejores resultados con ese método de prueba.

Ericson2314
fuente
1

Incluya siempre los archivos de encabezado en el archivo CPP. Esto no solo acorta significativamente el tiempo de compilación, sino que también le ahorra muchos problemas si decide utilizar encabezados precompilados. En mi experiencia, vale la pena practicar incluso el problema de las declaraciones a futuro. Romper la regla solo cuando sea necesario.

Gvozden
fuente
¿Puede explicar cómo esto "acortará significativamente el tiempo de compilación"?
Mawg dice que reinstalar a Monica
1
Esto garantiza que cada unidad de compilación (archivo CPP) solo obtenga el mínimo de archivos de inclusión en el tiempo de compilación. De lo contrario, si coloca las inclusiones en los archivos H, se forman rápidamente cadenas de dependencia falsas y termina obteniendo todas las inclusiones con cada compilación.
Gvozden
Con incluir guardias, podría cuestionarme "significativamente", pero supongo que el acceso al disco (para descubrir esas protecciones inlcuidas) es "lento", así que cederá el punto. Gracias por aclarar
Mawg dice que reinstale a Monica
0

Yo diría que hay ventajas y desventajas de esta convención. Por un lado, es bueno saber exactamente qué incluye su archivo .cpp. Por otro lado, la lista de incluye puede crecer fácilmente a un tamaño ridículo.

Una forma de fomentar esta convención es no incluir nada en sus propios encabezados, sino solo en los archivos .cpp. Entonces, cualquier archivo .cpp que use su encabezado no se compilará a menos que incluya explícitamente todos los demás encabezados de los que depende.

Probablemente se requiere algún compromiso razonable aquí. Por ejemplo, puede decidir que está bien incluir encabezados de biblioteca estándar dentro de sus propios encabezados, pero no más.

Dima
fuente
3
Si deja las cabeceras fuera de los encabezados, incluir el orden comienza a importar ... y definitivamente quiero evitar eso.
M. Dudley
1
-1: Crea una pesadilla de dependencia. Una mejora de xh podría requerir un cambio en CADA archivo .cpp que incluye xh
kevin cline
1
@ M.Dudley, como dije, hay ventajas y desventajas.
Dima