2010-06-23 5 views
6

He estado trabajando con C++ durante un par de semanas, pero el mecanismo detrás de los archivos de cabecera (¿o el enlazador, supongo?) Me confunde. He adquirido el hábito de crear un "main.h" para agrupar mis otros archivos de cabecera y mantener el main.cpp ordenado, pero a veces esos archivos de cabecera se quejan de que no se puede encontrar un archivo de cabecera diferente (aunque esté declarado) en el "main.h"). Probablemente no estoy explicando muy bien lo que aquí es una versión abreviada de lo que estoy tratando de hacer:¿Alguien puede ayudar a aclarar cómo funcionan los archivos de cabecera?

//main.cpp 

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

-

//main.h 

#include "player.h" 
#include "health.h" 
#include "custvector.h" 

-

//player.h 

#include "main.h" 
class Player { 
    private: 
     Vector playerPos; 
    public: 
     Health playerHealth; 
}; 

-

//custvector.h 

struct Vector { 
    int X; 
    int Y; 
    int Z; 
}; 

-

//health.h 
class Health { 
    private: 
     int curHealth; 
     int maxHealth; 
    public: 
     int getHealth() const; 
     void setHealth(int inH); 
     void modHealth(int inHM); 
}; 

No incluiré health.cpp porque es un poco largo (pero hace el trabajo), tiene #include "health.h".

De todos modos, el compilador (Código :: Bloques) se queja de que "player.h" no puede encontrar los tipos 'Health' o 'Vector'. Pensé que si usaba #include "main.h" en "player.h" podría encontrar las definiciones para Health y Vector sentido que están incluidas en "main.h". Pensé que, de alguna manera, tendrían un túnel (player.h -> main.h -> health.h). Pero eso no funcionó muy bien. ¿Hay algún tipo de diagrama o video que pueda aclarar cómo se debe configurar esto? Google no fue de mucha ayuda (ni mi libro).

+1

No responde su pregunta, pero debe cambiar la Estructura vectorial por un nombre diferente. Se volverá confuso cuando empiece a usar std :: vector, y un punto en el espacio 3D no es realmente un vector de todos modos. – reuscam

+0

Gracias, lo haré. Y tienes razón, debería haber sido Point o algo así. –

+0

En realidad, un vector se define solo por un punto. – Spidey

Respuesta

8

Las otras respuestas aquí han explicado de manera efectiva cómo funcionan los archivos de cabecera y el preprocesador. El problema más grande que tienes es la dependencia circular, que por experiencia, sé que puede ser un dolor real. Además, cuando eso comienza a suceder, el compilador comienza a comportarse de maneras muy extrañas y lanza mensajes de error que no son muy útiles. El método que me enseñaron por el gurú de C++ en la universidad era comenzar cada archivo (un archivo de cabecera por ejemplo) con

//very beginning of the file 
#ifndef HEADER_FILE_H //use a name that is unique though!! 
#define HEADER_FILE_H 
... 
//code goes here 
... 
#endif 
//very end of the file 

Esto utiliza las directivas de preprocesador para evitar automáticamente las dependencias circulares. Básicamente, siempre uso una versión en mayúsculas del nombre del archivo. custom-vector.h convierte

#ifndef CUSTOM_VECTOR_H 
#define CUSTOM_VECTOR_H 

Esto le permite incluir archivos Willie-Nillie sin crear dependencias circulares porque si un archivo se incluye varias veces, su variable de preprocesador ya está definido, por lo que el preprocesador se salta el archivo. También hace que sea más fácil trabajar con el código más tarde porque no tiene que pasar por los viejos archivos de encabezado para asegurarse de que no haya incluido algo. Sin embargo, repetiré una vez más, asegúrese de que los nombres de las variables que usa en sus declaraciones #define son únicos para usted; de lo contrario, podría tener problemas en los que algo no se incluye correctamente ;-).

¡Buena suerte!

+0

Espero que no te importe: he editado tu ejemplo CUSTOM_VECTOR_CPP . Los archivos '* .c' y' * .cpp' no necesitan incluir guardias ya que no los #includes. –

+0

Entonces, por ejemplo, si tuviera que hacer una clase de Enemigo que necesita Vector y Salud, y la clase de Jugador ya incluye Vector y Salud, no tendría que incluirlos para Enemigo? –

+0

@John ¡No hay problema!Buena idea, estoy acostumbrado a la programación de plantillas donde tienes que incluir los archivos * .cpp en los archivos * .h. Acabo de adquirir el hábito de hacerlo :-) –

3

Tiene una dependencia circular. El jugador incluye main.h, pero main.h incluye player.h. Resuelva esto eliminando una dependencia u otra. \

Player.h debe incluir health.h y custvector.h, y en este momento, no creo que main.h necesite incluir ninguno. Eventualmente puede necesitar player.h.

+1

#ifndef ... #endif es otra manera, un poco más fácil de ir también –

+0

¿No será eso un problema si algo más necesita usar health.h? ¿Eso no lo incluiría en el programa dos veces? –

+1

La forma de evitar incluir un archivo de encabezado varias veces en C o C++ es usar un protector de inclusión. Cada archivo de encabezado comienza con las dos líneas '#ifndef UNIQUE_STRING' y' #define UNIQUE_STRING' (reemplazando a UNIQUE_STRING con algún nombre que usted no '# define' en otro lugar). El archivo luego termina con '# endif'. http://en.wikipedia.org/wiki/Include_guard –

2

incluye el trabajo muy simple, solo ordenan el preprocesador para agregar el contenido del archivo a donde se establece incluir. La idea básica es incluir encabezados de los que dependas. en player.h debe incluir custvector.h y Health.h. En main solo player.h, porque todo lo necesario se llevará con el jugador. y no necesita incluir main.h en player.h en absoluto.

También es bueno asegurarse de que el encabezado esté incluido solo una vez. en esta pregunta, la solución general se da How to prevent multiple definitions in C? en el caso de Visual Studio puede usar #pragma once, si Borland C++ también hay un truco, pero lo olvidé.

+0

Supongo que eso es lo que me preocupaba, no quería agregarlo dos veces si decidía hacer algo así como una clase Enemy que también necesitaba Vector and Health. Pensé que si ponía todas las definiciones en main.h, eliminaría ese problema. –

+0

@Karl Menke 'main.h' no es un buen lugar para ponerlo, ya que también contendrá cosas para main.cpp. deberías crear algo como 'common.h' y mover las referencias comunes allí. luego úsalo. – Andrey

10

La mejor manera de pensar en los archivos de encabezado es como "copiar y pegar automáticamente".

Una buena manera de pensar sobre esto (aunque no es cómo esto se implementa realmente) es que cuando compila un archivo C o un archivo C++, el preprocesador se ejecuta primero. Cada vez que encuentra una instrucción #include, en realidad pegará el contenido de ese archivo en lugar de la instrucción #include. Esto se hace hasta que ya no haya más. El búfer final se pasa al compilador.

Esto introduce varias complejidades:

primer lugar, si A.H incluye B.H y B.H incluye A.h, tienes un problema. Porque cada vez que quiera pegar A, necesitaría B e internamente tendría A! Esa es una recursión. Por este motivo, los archivos de encabezado usan #ifndef, para garantizar que la misma parte no se lea varias veces. Esto probablemente esté sucediendo en tu código.

En segundo lugar, su compilador de C lee el archivo después de que todos los archivos de encabezado hayan sido "aplanados", por lo que debe tenerlo en cuenta al razonar sobre lo que se declara antes de qué.

+0

Esa es la explicación que más me gusta, y la que mejor describo cómo entiendo este problema. Es simple, el compilador toma texto y lo compila. El preprocesador toma el texto y lo preprocesa. Incluye solo una directiva de preprocesador para IMPORTAR texto de un archivo externo en el archivo actual. – Spidey

+0

Gracias, eso ayuda. –

+0

+1 Simple y genial .. :) – liaK

1

¿Hay algún tipo de diagrama o video que pueda aclarar cómo se debe configurar esto?

Pruebe mi respuesta a la pregunta "Clean up your #include statements?".

+0

Gracias, voy a echarle un vistazo a eso. –

+0

@Karl Menke - Hay otro [aquí] (http://stackoverflow.com/questions/1598207/odd-circular-dependency-issue/1598257#1598257) – ChrisW

1

Quiere organizar su #includes (y bibliotecas, para el caso) en un DAG (gráfico acíclico dirigido). Esa es la manera complicada de decir "evitar ciclos entre los archivos de cabecera":

Si B incluye A, A no debe incluir B.

Por lo tanto, el uso de "un gran maestro main.h" no es el enfoque correcto, porque es difícil de #incluir solo dependencias directas.

Cada archivo .cpp debe incluir su propio archivo .h. Ese archivo .h solo debe incluir elementos que él mismo necesita para compilar.

Generalmente no hay main.h, porque main.cpp nadie necesita la definición de main.

Además, querrá include guards para protegerse contra múltiples inclusiones.

Por ejemplo

//player.h 
#ifndef PLAYER_H_ 
#define PLAYER_H_ 
#include "vector.h" // Because we use Vector 
#include "health.h" // Because we use Health 
class Player { 
    private: 
     Vector playerPos; 
    public: 
     Health playerHealth; 
}; 
#endif 

-

//vector.h 
#ifndef VECTOR_H_ 
#define VECTOR_H_ 
struct Vector { 
    int X; 
    int Y; 
    int Z; 
}; 
#endif 

-

//health.h 
#ifndef HEALTH_H_ 
#define HEALTH_H_ 
class Health { 
    private: 
     int curHealth; 
     int maxHealth; 
    public: 
     int getHealth() const; 
     void setHealth(int inH); 
     void modHealth(int inHM); 
}; 
#endif 

La única vez que desee agregar un montón de #include s en una sola cabecera es cuando estás proporcionándolo como una conveniencia para una biblioteca muy grande.

En su ejemplo actual, está yendo un poco por la borda: cada clase no necesita su propio archivo de encabezado. Es puede todo entra en main.cpp.

El preprocesador c literalmente inserta el archivo de #include en el archivo que lo incluye (a menos que ya haya sido insertado, por lo que necesita las protecciones incluidas). Le permite usar las clases definidas en esos archivos porque ahora tiene acceso a su definición.

Cuestiones relacionadas