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::string
o std::vector<SomeType>
. Tienes que #include
esos 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í bar
hay un Foo
miembro 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.o
con una herramienta como nm
, parecerá que las funciones foo.cc
están llamando a todo tipo de cosas para las que no ha hecho lo apropiado #include
. Entonces, ¿debería agregar #include
directivas 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.
#include "x.h"
funcionará sin requerir algo de precedente#include
. Eso es lo suficientemente bueno si no abusas#define
.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".
fuente
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.fuente
no
#Pragma once
logra esto? Puede incluir algo tantas veces como lo desee, ya sea directamente o mediante encadenamientos incluidos, y siempre que haya#Pragma once
uno 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.
#ifdef
La cadena incluye para obtener los mejores resultados con ese método de prueba.fuente
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.
fuente
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.
fuente