web-dev-qa-db-fra.com

C ++ #include gardes

RESOLU

Ce qui m'a vraiment aidé, c'est que je pouvais #inclure des en-têtes dans le fichier .cpp sans provoquer l'erreur redéfinie.


Je suis nouveau en C++ mais j'ai une certaine expérience de programmation en C # et Java donc je pourrais manquer quelque chose de basique unique à C++.

Le problème est que je ne sais pas vraiment ce qui ne va pas, je vais coller du code pour essayer d'expliquer le problème.

J'ai trois classes, GameEvents, Physics et GameObject. J'ai des en-têtes pour chacun d'eux. GameEvents a une physique et une liste de GameObjects. La physique a une liste de GameObjects.

Ce que j'essaie de réaliser, c'est que je veux que GameObject puisse accéder à un objet physique ou en être propriétaire.

Si j'inclue simplement "Physics.h" dans GameObject, j'obtiens "l'erreur C2111: 'ClassXXX': redéfinition du type 'class'" que je comprends. Et c'est là que je pensais que # include-guards aiderait donc j'ai ajouté un include guard à mon Physics.h puisque c'est l'en-tête que je veux inclure deux fois.

Voilà à quoi ça ressemble

#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

Mais si j'inclus "Physics.h" dans mon GameObject.h maintenant comme ceci:

#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();
};

Je reçois plusieurs problèmes qui ne comprennent pas pourquoi ils apparaissent. Si je n'inclue pas "Physics.h", mon code fonctionne très bien.

Je suis très reconnaissant pour toute aide.

39
Orujimaru

Le préprocesseur est un programme qui prend votre programme, apporte quelques modifications (par exemple, inclure des fichiers (#include), une expansion de macro (#define), et essentiellement tout ce qui commence par #) et donne le résultat "clean" au compilateur.

Le préprocesseur fonctionne comme ceci quand il voit #include:

Lorsque vous écrivez:

#include "some_file"

Le contenu de some_file obtient presque littéralement la copie collée dans le fichier, y compris. Maintenant, si vous avez:

a.h:
class A { int a; };

Et:

b.h:
#include "a.h"
class B { int b; };

Et:

main.cpp:
#include "a.h"
#include "b.h"

Vous recevez:

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"

Vous pouvez maintenant voir comment A est redéfini.

Lorsque vous écrivez des gardes, ils deviennent comme ceci:

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

Voyons maintenant comment #includes en principal serait développé (c'est exactement comme dans le cas précédent: copier-coller)

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

Maintenant, suivons le préprocesseur et voyons quel "vrai" code en sort. J'irai ligne par ligne:

// From #include "a.h"

Commentaire. Ignorer! Continuer:

#ifndef A_H

Est A_H défini? Non! Continuez ensuite:

#define A_H

OK maintenant A_H est défini. Continuer:

class A { int a; };

Ce n'est pas quelque chose pour le préprocesseur, alors laissez-le tranquille. Continuer:

#endif

Le précédent if s'est terminé ici. Continuer:

// From #include "b.h"

Commentaire. Ignorer! Continuer:

#ifndef B_H

Est B_H défini? Non! Continuez ensuite:

#define B_H

OK maintenant B_H est défini. Continuer:

#ifndef A_H          // From

Est A_H défini? OUI! Ignorez ensuite jusqu'à ce que #endif:

#define A_H          // #include "a.h"

Ignorer

class A { int a; };  // inside

Ignorer

#endif               // "b.h"

Le précédent if s'est terminé ici. Continuer:

class B { int b; };

Ce n'est pas quelque chose pour le préprocesseur, alors laissez-le tranquille. Continuer:

#endif

Le précédent if s'est terminé ici.

Autrement dit, une fois que le préprocesseur a terminé avec le fichier, voici ce que le compilateur voit:

main.cpp
class A { int a; };
class B { int b; };

Comme vous pouvez le voir, tout ce qui peut obtenir #included dans le même fichier deux fois, que ce soit directement ou indirectement doit être protégé. Puisque .h les fichiers sont toujours très susceptibles d'être inclus deux fois, c'est bien si vous gardez TOUS vos fichiers .h.

P.S. Notez que vous avez également une circulaire #includes. Imaginez le préprocesseur copiant-collant le code de Physics.h dans GameObject.h qui voit qu'il y a un #include "GameObject.h" ce qui signifie copier GameObject.h en lui-même. Lorsque vous copiez, vous obtenez à nouveau #include "Pysics.h" et vous êtes coincé dans une boucle pour toujours. Les compilateurs empêchent cela, mais cela signifie que votre #includes sont à moitié terminés.

Avant de dire comment résoudre ce problème, vous devez savoir autre chose.

Si tu as:

#include "b.h"

class A
{
    B b;
};

Ensuite, le compilateur doit tout savoir sur b, plus important encore, sur les variables qu'il possède, etc. afin de savoir combien d'octets il doit mettre à la place de b dans A .

Cependant, si vous avez:

class A
{
    B *b;
};

Ensuite, le compilateur n'a pas vraiment besoin de savoir quoi que ce soit sur B (puisque les pointeurs, quel que soit le type, ont la même taille). La seule chose qu'il doit savoir sur B, c'est qu'il existe!

Donc, vous faites quelque chose appelé "déclaration à terme":

class B;  // This line just says B exists

class A
{
    B *b;
};

Ceci est très similaire à beaucoup d'autres choses que vous faites dans les fichiers d'en-tête tels que:

int function(int x);  // This is forward declaration

class A
{
public:
    void do_something(); // This is forward declaration
}
118
Shahbaz

Vous avez des références circulaires ici: Physics.h comprend GameObject.h qui comprend Physics.h. Votre classe Physics utilise GameObject* (pointeur), vous n'avez donc pas besoin d'inclure GameObject.h dans Physics.h mais utilisez simplement la déclaration directe - au lieu de

#include "GameObject.h" 

mettre

class GameObject;   

De plus, placez des gardes dans chaque fichier d'en-tête.

5
Bojan Komazec

Le problème est que votre GameObject.h n'a pas de gardes, donc quand vous #include "GameObject.h" dans Physics.h il est inclus lorsque GameObject.h comprend Physics.h.

4
bitmask

Ajoutez des gardes dans tous vos *.h ou *.hh fichiers d'en-tête (sauf si vous avez des raisons spécifiques de ne pas le faire).

Pour comprendre ce qui se passe, essayez d'obtenir la forme prétraitée de votre code source. Avec GCC, c'est quelque chose comme g++ -Wall -C -E yourcode.cc > yourcode.i (Je n'ai aucune idée de la façon dont les compilateurs Microsoft font cela). Vous pouvez également demander quels fichiers sont inclus, avec GCC comme g++ -Wall -H -c yourcode.cc

4

Tout d'abord, vous devez également inclure des gardes sur gameobject, mais ce n'est pas le vrai problème ici

Si autre chose inclut physics.h en premier, physics.h inclut gameobject.h, vous obtenez quelque chose comme ceci:

class GameObject {
...
};

#include physics.h

class Physics {
...
};

et le #include physics.h est supprimé à cause des gardes d'inclusion, et vous vous retrouvez avec une déclaration de GameObject avant la déclaration de physique.

Mais c'est un problème si vous voulez que GameObject ait un pointeur vers une physique, car pour htat, la physique devra être déclarée en premier.

Pour résoudre le cycle, vous pouvez déclarer une classe à la place, mais uniquement si vous ne l'utilisez que comme pointeur ou référence dans la déclaration suivante, à savoir:

#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
4
je4d

Utilisez des gardes d'inclusion dans [~ # ~] tous [~ # ~] vos fichiers d'en-tête. Puisque vous utilisez Visual Studio, vous pouvez utiliser le #pragma once comme première définition de préprocesseur dans tous vos en-têtes.

Cependant, je suggère d'utiliser l'approche classique:

#ifndef CLASS_NAME_H_
#define CLASS_NAME_H_

// Header code here

#endif //CLASS_NAME_H_

Lisez ensuite à propos de forward declaration et appliquez-le.

3
pnezis

Le but d'un protège-en-tête est d'éviter d'inclure plusieurs fois le même fichier. Mais la protection d'en-tête actuellement utilisée en C++ peut être améliorée. Le gardien actuel est:

#ifndef AAA_H
#define AAA_H

class AAA
{ /* ... */ };

#endif

Ma nouvelle proposition de garde est:

#ifndef AAA_H
#define AAA_H

class AAA
{ /* ... */ };

#else
class AAA;  // Forward declaration
#endif

Cela résout le problème ennuyeux qui se produit lorsque la classe AAA a besoin de la déclaration de classe BBB, tandis que la classe BBB a besoin de la déclaration de classe AAA, généralement parce qu'il y a des pointeurs croisés d'une classe à l'autre:

// File AAA.h
#ifndef AAA_H
#define AAA_H

#include "BBB.h"

class AAA
{ 
  BBB *bbb;
/* ... */ 
};

#else
class AAA;  // Forward declaration
#endif

//+++++++++++++++++++++++++++++++++++++++

// File BBB.h
#ifndef BBB_H
#define BBB_H

#include "AAA.h"

class BBB
{ 
  AAA *aaa;
/* ... */ 
};

#else
class BBB;  // Forward declaration
#endif

J'aimerais que cela soit inclus dans les IDE qui génèrent automatiquement du code à partir de modèles.

0
angarciaba