¿Por qué incluir guardias no hace que funcione una circular #include?

Resuelto Orujimaru asked hace 12 años • 8 respuestas

Tengo tres clases GameEvents: Physicsy GameObject. Tengo encabezados para cada uno de ellos.

  • GameEventstiene uno Physicsy una lista de GameObjects.
  • Physicstiene una lista de GameObjects.

Tenga en cuenta la dependencia circular. Estoy tratando de lograr que GameObjectsea capaz de acceder o poseer unPhysics objeto.

Si simplemente #include "Physics.h"entro GameObject, entiendo error C2111: 'ClassXXX' : 'class' type redifinitionlo 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.hahora 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.

Orujimaru avatar Nov 05 '11 19:11 Orujimaru
Aceptado

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_filecasi 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 #includese 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_Hdefine? ¡No! Luego continúa:

#define A_H

Ok, ahora A_Hestá definido. Continuar:

class A { int a; };

Esto no es algo para preprocesador, así que déjalo así. Continuar:

#endif

Lo anterior ifterminó aquí. Continuar:

// From #include "b.h"

Comentario. ¡Ignorar! Continuar:

#ifndef B_H

¿ Se B_Hdefine? ¡No! Luego continúa:

#define B_H

Ok, ahora B_Hestá definido. Continuar:

#ifndef A_H          // From

¿ Se A_Hdefine? ¡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 ifterminó aquí. Continuar:

class B { int b; };

Esto no es algo para preprocesador, así que déjalo así. Continuar:

#endif

Lo anterior ifterminó 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 #includeguardarse en el mismo archivo dos veces, ya sea directa o indirectamente, debe protegerse. Dado que .hes 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 #includes 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.hen 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 #includecorreos 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 bde 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 Bes 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
}
Shahbaz avatar Nov 05 '2011 12:11 Shahbaz

Tenéis referencias circulares aquí: Physics.hincluye GameObject.hque incluye Physics.h. Su clase Physicsusa GameObject*el tipo (puntero), por lo que no necesita incluirlo GameObject.h, Physics.hsolo use la declaración directa, en lugar de

#include "GameObject.h" 

poner

class GameObject;   

Además, coloque protectores en cada archivo de encabezado.

Bojan Komazec avatar Nov 05 '2011 12:11 Bojan Komazec

El problema es que GameObject.hno tiene guardias, por lo que cuando ingresa, #include "GameObject.h"se Physics.hincluye cuando GameObject.hincluye Physics.h.

bitmask avatar Nov 05 '2011 12:11 bitmask

Agregue protectores de inclusión en todos sus archivos *.ho *.hharchivos 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

Basile Starynkevitch avatar Nov 05 '2011 12:11 Basile Starynkevitch

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
je4d avatar Nov 05 '2011 12:11 je4d