¿Por qué incluir guardias no hace que funcione una circular #include?
Tengo tres clases GameEvents
: Physics
y GameObject
. Tengo encabezados para cada uno de ellos.
GameEvents
tiene unoPhysics
y una lista deGameObjects
.Physics
tiene una lista deGameObjects
.
Tenga en cuenta la dependencia circular. Estoy tratando de lograr que GameObject
sea capaz de acceder o poseer unPhysics
objeto.
Si simplemente #include "Physics.h"
entro GameObject
, entiendo
error C2111: 'ClassXXX' : 'class' type redifinition
lo que entiendo. Aquí es donde pensé que incluir guardias ayudaría, así que agregué una guardia de inclusión a miPhysics.h
ya que ese es el encabezado que quiero incluir dos veces.
así es como se ve
#ifndef PHYSICS_H
#define PHYSICS_H
#include "GameObject.h"
#include <list>
class Physics
{
private:
double gravity;
list<GameObject*> objects;
list<GameObject*>::iterator i;
public:
Physics(void);
void ApplyPhysics(GameObject*);
void UpdatePhysics(int);
bool RectangleIntersect(SDL_Rect, SDL_Rect);
Vector2X CheckCollisions(Vector2X, GameObject*);
};
#endif // PHYSICS_H
Pero si #include "Physics.h"
en mi GameObject.h
ahora me gusta esto:
#include "Texture2D.h"
#include "Vector2X.h"
#include <SDL.h>
#include "Physics.h"
class GameObject
{
private:
SDL_Rect collisionBox;
public:
Texture2D texture;
Vector2X position;
double gravityForce;
int weight;
bool isOnGround;
GameObject(void);
GameObject(Texture2D, Vector2X, int);
void UpdateObject(int);
void Draw(SDL_Surface*);
void SetPosition(Vector2X);
SDL_Rect GetCollisionBox();
};
Recibo varios problemas que no entiendo por qué aparecen. Si no lo hago, #include "Physics.h"
mi código funciona bien.
El preprocesador es un programa que toma su programa, realiza algunos cambios (por ejemplo, incluye archivos (#include), expansión de macros (#define) y básicamente todo lo que comienza con#
) y le da el resultado "limpio" al compilador.
El preprocesador funciona así cuando ve#include
:
Cuando escribes:
#include "some_file"
El contenido de some_file
casi literalmente se copia y pega en el archivo que lo incluye. Ahora si tienes:
a.h:
class A { int a; };
Y:
b.h:
#include "a.h"
class B { int b; };
Y:
main.cpp:
#include "a.h"
#include "b.h"
Usted obtiene:
main.cpp:
class A { int a; }; // From #include "a.h"
class A { int a; }; // From #include "b.h"
class B { int b; }; // From #include "b.h"
Ahora puedes ver cómoA
se redefine.
Cuando escribes guardias, se vuelven así:
a.h:
#ifndef A_H
#define A_H
class A { int a; };
#endif
b.h:
#ifndef B_H
#define B_H
#include "a.h"
class B { int b; };
#endif
Así que ahora veamos cómo #include
se expandiría s en main (esto es exactamente como el caso anterior: copiar y pegar)
main.cpp:
// From #include "a.h"
#ifndef A_H
#define A_H
class A { int a; };
#endif
// From #include "b.h"
#ifndef B_H
#define B_H
#ifndef A_H // From
#define A_H // #include "a.h"
class A { int a; }; // inside
#endif // "b.h"
class B { int b; };
#endif
Ahora sigamos el preprocesador y veamos qué código "real" surge de esto. Iré línea por línea:
// From #include "a.h"
Comentario. ¡Ignorar! Continuar:
#ifndef A_H
¿ Se A_H
define? ¡No! Luego continúa:
#define A_H
Ok, ahora A_H
está definido. Continuar:
class A { int a; };
Esto no es algo para preprocesador, así que déjalo así. Continuar:
#endif
Lo anterior if
terminó aquí. Continuar:
// From #include "b.h"
Comentario. ¡Ignorar! Continuar:
#ifndef B_H
¿ Se B_H
define? ¡No! Luego continúa:
#define B_H
Ok, ahora B_H
está definido. Continuar:
#ifndef A_H // From
¿ Se A_H
define? ¡SÍ! Luego ignora hasta que corresponda #endif
:
#define A_H // #include "a.h"
Ignorar
class A { int a; }; // inside
Ignorar
#endif // "b.h"
Lo anterior if
terminó aquí. Continuar:
class B { int b; };
Esto no es algo para preprocesador, así que déjalo así. Continuar:
#endif
Lo anterior if
terminó aquí.
Es decir, una vez que el preprocesador termina con el archivo, esto es lo que ve el compilador:
main.cpp
class A { int a; };
class B { int b; };
Como puede ver, cualquier cosa que pueda #include
guardarse en el mismo archivo dos veces, ya sea directa o indirectamente, debe protegerse. Dado que .h
es muy probable que los archivos siempre se incluyan dos veces, es bueno proteger TODOS sus archivos .h.
PD: Tenga en cuenta que también tiene #include
s circulares. Imagine que el preprocesador copia y pega el código de Physics.h en GameObject.h y ve que hay algo #include "GameObject.h"
que significa copiar GameObject.h
en sí mismo. Cuando copias, vuelves a obtener #include "Pysics.h"
y quedas atrapado en un bucle para siempre. Los compiladores evitan eso, pero eso significa que sus #include
correos electrónicos están a medio hacer.
Antes de decir cómo solucionar este problema, debes saber otra cosa.
Si usted tiene:
#include "b.h"
class A
{
B b;
};
Luego, el compilador necesita saber todo b
, lo más importante, qué variables tiene, etc., para saber cuántos bytes debe colocar en lugar b
de A
.
Sin embargo, si tienes:
class A
{
B *b;
};
Entonces el compilador realmente no necesita saber nada B
(ya que los punteros, independientemente del tipo, tienen el mismo tamaño). ¡Lo único que necesita saber B
es que existe!
Entonces haces algo llamado "declaración directa":
class B; // This line just says B exists
class A
{
B *b;
};
Esto es muy similar a muchas otras cosas que haces en los archivos de encabezado, como:
int function(int x); // This is forward declaration
class A
{
public:
void do_something(); // This is forward declaration
}
Tenéis referencias circulares aquí: Physics.h
incluye GameObject.h
que incluye Physics.h
. Su clase Physics
usa GameObject*
el tipo (puntero), por lo que no necesita incluirlo GameObject.h
, Physics.h
solo use la declaración directa, en lugar de
#include "GameObject.h"
poner
class GameObject;
Además, coloque protectores en cada archivo de encabezado.
El problema es que GameObject.h
no tiene guardias, por lo que cuando ingresa, #include "GameObject.h"
se Physics.h
incluye cuando GameObject.h
incluye Physics.h
.
Agregue protectores de inclusión en todos sus archivos *.h
o *.hh
archivos de encabezado (a menos que tenga razones específicas para no hacerlo).
Para comprender lo que está sucediendo, intente obtener la forma preprocesada de su código fuente. Con GCC, es algo así como g++ -Wall -C -E yourcode.cc > yourcode.i
(no tengo idea de cómo hacen eso los compiladores de Microsoft). También puede preguntar qué archivos están incluidos, con GCC comog++ -Wall -H -c yourcode.cc
En primer lugar, también debes incluir guardias en el objeto del juego, pero ese no es el verdadero problema aquí.
Si algo más incluye primero la física.h, la física.h incluye el objeto del juego.h, obtendrás algo como esto:
class GameObject {
...
};
#include physics.h
class Physics {
...
};
y #includephysics.h se descarta debido a las protecciones de inclusión, y terminas con una declaración de GameObject antes de la declaración de Física.
Pero eso es un problema si quieres que GameObject tenga un puntero a Física, porque para eso la física tendría que declararse primero.
Para resolver el ciclo, puedes declarar una clase en su lugar, pero solo si solo la estás usando como puntero o referencia en la siguiente declaración, es decir:
#ifndef PHYSICS_H
#define PHYSICS_H
// no need for this now #include "GameObject.h"
#include <list>
class GameObject;
class Physics
{
private:
list<GameObject*> objects;
list<GameObject*>::iterator i;
public:
void ApplyPhysics(GameObject*);
Vector2X CheckCollisions(Vector2X, GameObject*);
};
#endif // PHYSICS_H