RISOLTO
Ciò che mi ha veramente aiutato è stato che potevo #includere le intestazioni nel file .cpp senza causare l’errore ridefinito.
Sono nuovo di C ++, ma ho una certa esperienza di programmazione in C # e Java, quindi potrei mancare qualcosa di base che è unico per C ++.
Il problema è che non so davvero cosa c’è che non va, quindi incollerò del codice per cercare di spiegare il problema.
Ho tre Classes, GameEvents, Physics e GameObject. Ho intestazioni per ciascuno di essi. GameEvents ha una fisica e un elenco di oggetti GameObjects. La fisica ha un elenco di oggetti GameObjects.
Quello che sto cercando di ottenere è che voglio che GameObject sia in grado di accedere o possedere un object di fisica.
Se ho semplicemente #include “Physics.h” in GameObject ottengo l’errore “C2111: ‘ClassXXX’: ‘class’ type redifinition” che capisco. Ed è qui che pensavo che le # include-guards mi avrebbero aiutato così ho aggiunto una guardia di inclusione al mio Physics.h dal momento che questa è l’intestazione che voglio includere due volte.
Ecco come appare
#ifndef PHYSICS_H #define PHYSICS_H #include "GameObject.h" #include class Physics { private: double gravity; list objects; list::iterator i; public: Physics(void); void ApplyPhysics(GameObject*); void UpdatePhysics(int); bool RectangleIntersect(SDL_Rect, SDL_Rect); Vector2X CheckCollisions(Vector2X, GameObject*); }; #endif // PHYSICS_H
Ma se ho #include “Physics.h” nel mio GameObject.h ora mi piace questo:
#include "Texture2D.h" #include "Vector2X.h" #include #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(); };
Ottengo più problemi che non capisco perché vengano visualizzati. Se non lo faccio #include “Physics.h” il mio codice funziona perfettamente.
Sono molto grato per qualsiasi aiuto.
Il preprocessore è un programma che prende il tuo programma, apporta alcune modifiche (ad esempio include i file (#include), l’espansione delle macro (#define) e in pratica tutto ciò che inizia con #
) e restituisce il risultato “pulito” al compilatore.
Il preprocessore funziona come questo quando vede #include
:
Quando scrivi:
#include "some_file"
I contenuti di some_file
letteralmente copiati nel file incluso. Ora se hai:
ah: class A { int a; };
E:
bh: #include "ah" class B { int b; };
E:
main.cpp: #include "ah" #include "bh"
Ottieni:
main.cpp: class A { int a; }; // From #include "ah" class A { int a; }; // From #include "bh" class B { int b; }; // From #include "bh"
Ora puoi vedere come A
viene ridefinito.
Quando scrivi guardie, diventano così:
ah: #ifndef A_H #define A_H class A { int a; }; #endif bh: #ifndef B_H #define B_H #include "ah" class B { int b; }; #endif
Quindi ora vediamo come #include
s in main sarebbe espanso (questo è esattamente, come nel caso precedente: copia-incolla)
main.cpp: // From #include "ah" #ifndef A_H #define A_H class A { int a; }; #endif // From #include "bh" #ifndef B_H #define B_H #ifndef A_H // From #define A_H // #include "ah" class A { int a; }; // inside #endif // "bh" class B { int b; }; #endif
Ora seguiamo il preprocessore e vediamo che cosa viene fuori dal codice “reale”. Andrò linea per linea:
// From #include "ah"
Commento. Ignorare! Continua:
#ifndef A_H
A_H
è definito? No! Quindi continua:
#define A_H
Ok ora A_H
è definito. Continua:
class A { int a; };
Questo non è qualcosa per il preprocessore, quindi lascia perdere. Continua:
#endif
Il precedente if
finito qui. Continua:
// From #include "bh"
Commento. Ignorare! Continua:
#ifndef B_H
B_H
è definito? No! Quindi continua:
#define B_H
Ok ora B_H
è definito. Continua:
#ifndef A_H // From
A_H
è definito? SÌ! Quindi ignora fino al corrispondente #endif
:
#define A_H // #include "ah"
Ignorare
class A { int a; }; // inside
Ignorare
#endif // "bh"
Il precedente if
finito qui. Continua:
class B { int b; };
Questo non è qualcosa per il preprocessore, quindi lascia perdere. Continua:
#endif
Il precedente if
finito qui.
Cioè, dopo che il preprocessore ha finito con il file, questo è ciò che il compilatore vede:
main.cpp class A { int a; }; class B { int b; };
Quindi, come puoi vedere, tutto ciò che può ottenere #include
d nello stesso file due volte, se direttamente o indirettamente deve essere sorvegliato. Dal momento che i file .h
sono sempre molto probabilmente inclusi due volte, è un bene se si custodiscono TUTTI i file .h.
PS Nota che hai anche #include
s circolare. Immagina il preprocessore copiando il codice di Physics.h in GameObject.h che vede che c’è un #include "GameObject.h"
che significa copiare GameObject.h
in se stesso. Quando copi, ottieni di nuovo #include "Pysics.h"
e sei bloccato in un ciclo per sempre. I compilatori lo impediscono, ma ciò significa che i tuoi #include
sono a metà.
Prima di dire come risolvere il problema, dovresti sapere un’altra cosa.
Se hai:
#include "bh" class A { B b; };
Quindi il compilatore ha bisogno di sapere tutto su b
, soprattutto, quali variabili ha ecc. In modo che sappia quanti byte dovrebbe mettere al posto di b
in A
Tuttavia, se hai:
class A { B *b; };
Quindi il compilatore non ha davvero bisogno di sapere nulla di B
(poiché i puntatori, indipendentemente dal tipo hanno le stesse dimensioni). L’unica cosa che deve sapere su B
è che esiste!
Quindi fai qualcosa chiamato “forward statement”:
class B; // This line just says B exists class A { B *b; };
Questo è molto simile a molte altre cose che fai nei file header come:
int function(int x); // This is forward declaration class A { public: void do_something(); // This is forward declaration }
Hai riferimenti circolari qui: Physics.h
include GameObject.h
che include Physics.h
. La tua class Physics
usa il tipo GameObject*
(puntatore) in modo da non dover includere GameObject.h
in Physics.h
ma usa solo la dichiarazione forward – invece di
#include "GameObject.h"
mettere
class GameObject;
Inoltre, metti guardie in ogni file di intestazione.
Il problema è che il tuo GameObject.h
non ha guardie, quindi quando #include "GameObject.h"
in Physics.h
viene incluso quando GameObject.h
include Physics.h
.
Aggiungi guardie incluse in tutti i tuoi *.h
intestazione *.h
o *.hh
(a meno che tu non abbia motivi specifici per non farlo).
Per capire cosa sta succedendo, prova ad ottenere la forma preelaborata del tuo codice sorgente. Con GCC, è qualcosa come g++ -Wall -C -E yourcode.cc > yourcode.i
(non ho idea di come fanno i compilatori Microsoft). Puoi anche chiedere quali file sono inclusi, con GCC come g++ -Wall -H -c yourcode.cc
Per prima cosa è necessario includere anche le protezioni sul gameobject, ma non è questo il vero problema qui
Se qualcos’altro include physics.h, physics.h include gameobject.h, ottieni qualcosa di simile a questo:
class GameObject { ... }; #include physics.h class Physics { ... };
e la #include physics.h viene scartata a causa delle guardie incluse, e si finisce con una dichiarazione di GameObject prima della dichiarazione di Physics.
Ma questo è un problema se vuoi che GameObject abbia un puntatore a una fisica, perché per la fisica htat dovrebbe essere dichiarato per primo.
Per risolvere il ciclo, puoi inoltrare una class invece, ma solo se la stai usando come puntatore o riferimento nella seguente dichiarazione, cioè:
#ifndef PHYSICS_H #define PHYSICS_H // no need for this now #include "GameObject.h" #include class GameObject; class Physics { private: list objects; list::iterator i; public: void ApplyPhysics(GameObject*); Vector2X CheckCollisions(Vector2X, GameObject*); }; #endif // PHYSICS_H
Utilizzare include guardie in TUTTI i file di intestazione. Poiché utilizzi Visual Studio, puoi utilizzare #pragma once
come prima definizione del preprocessore in tutte le intestazioni.
Tuttavia suggerisco di usare l’approccio classico:
#ifndef CLASS_NAME_H_ #define CLASS_NAME_H_ // Header code here #endif //CLASS_NAME_H_
Seconda lettura sulla dichiarazione in avanti e applicazione.