multim Temat założony przez niniejszego użytkownika |
Bład linkowania wielu deklaracji zapowiadających » 2020-01-22 15:49:05 Piszę prostą grę bazując na SFML. Podczas refaktoryzacji jednej z klas (chciałem wyodrębnić jedno pole do innej klasy) po kompilacji pojawił się wprowadzający mnie w błąd komunikat: In file included from src/Game/Tanks/Map.hpp:5:0, from src/Game/Tanks/Projectile.hpp:5, from src/Game/Tanks/Tank.hpp:5, from src/Game/Tanks/Tank.cpp:1: src/Game/Tanks/Enemy.hpp:9:2: error: expected class-name before '{' token { ^ src/Game/Tanks/Enemy.hpp:16:37: error: 'Map' has not been declared sf::Vector2u &gameResolution, Map *map, sf::Texture bulletTexture, GameCore::Direction initialDirection); ^~~ src/Game/Tanks/Enemy.hpp: In constructor 'Tanks::Enemy::Enemy()': src/Game/Tanks/Enemy.hpp:14:14: error: class 'Tanks::Enemy' does not have any field named 'Tank' Enemy() : Tank(), direction(GameCore::Direction::NONE) { setRotation(0); } ^~~~ src/Game/Tanks/Enemy.hpp:14:61: error: 'setRotation' was not declared in this scope Enemy() : Tank(), direction(GameCore::Direction::NONE) { setRotation(0); }
Przyczyną najprawopodobniej jest błąd w kolejności dołączania plików. Może ktoś z forum byłby w stanie szybciej zauważyć dokładną powód błędu świeżym spojrzeniem? Problem pojawił się po zastąpieniu deklaracji #include "Tank.hpp" plikiem "Enemy.hpp". Enemy jest klasą pochodną Tank, instancje tych klas zawierają się w vectorach zdefiniowanych w Map. Klasa Map (plik nagłówkowy): #ifndef WALL_MAP_HPP #define WALL_MAP_HPP
#include "..\Core\BasicObject.hpp" #include "Enemy.hpp" #include <vector>
namespace Tanks { class Enemy; class Tank; class Map { private: std::vector < GameCore::BasicObject > walls; std::vector < Enemy > enemies; bool collide( sf::FloatRect collisionBox ); public: Map() { } virtual ~Map() { } void addWall( GameCore::BasicObject & wall ); void addEnemy( Enemy & enemy ); void clear(); void draw( sf::RenderWindow & renderWindow ); bool collide( const Tank * tank, const GameCore::Direction & direction ); }; }
#endif
początek pliku nagłówkowego klasy Tank #ifndef TANK_HPP #define TANK_HPP
#include <vector> #include "Projectile.hpp" #include "Tank.hpp" #include "Map.hpp"
namespace Tanks { class Map; class Projectile; class Tank : public GameCore::InteractiveObject początek pliku nagłówkowego klasy Enemy #ifndef ENEMY_HPP #define ENEMY_HPP
#include "Tank.hpp"
namespace Tanks { class Enemy : public Tank Nie mogę znaleźć źródła problemu, a niestety wyjątkowo w tej sytuacji nie pomaga mi komunikat błędu kompilacji (w podobnych przypadkach zazwyczaj dostawałem informacje, że klasa X nie jest zdefiniowana, lub nie ma "ciała"). |
|
pekfos |
» 2020-01-22 16:20:03 Masz cykl w include'ach. Deklaracje robi się po to, by nie dołączać plików z definicjami, a Ty robisz jedno i drugie. |
|
multim Temat założony przez niniejszego użytkownika |
» 2020-01-22 21:19:31 Jeżeli dobrze rozumiem, to rozwiązaniem mojego problemu jest usunięcie includów Projectile i Map z pliku nagłówkowego Tank i Enemy z Map?
Łączenie includów rozumiałem "intuicyjnie", ale z deklaracji zapowiadających nie pamiętam żebym wcześniej korzystał. Jeżeli coś pisałem w C++, to raczej były to malutkie programy z minimalną hierarchią.
|
|
pekfos |
» 2020-01-22 22:32:30 Musisz rozumieć, co robią narzędzia, z których korzystasz. Po pierwsze #include to jest dosłownie "kopiuj-wklej treść pliku". Twoje Tank.hpp dołącza samego siebie, co nie ma żadnego efektu przez include guardy jakie masz (te #ifndef, #define). Wtedy efektywnie tylko pierwsze wklejenie treści się liczy. Powiedzmy że masz pliki a.hpp#ifndef A_HPP #define A_HPP #include "b.hpp" struct A { B * b; }; #endif
i analogiczne b.hpp, które dołącza a.hpp i ma strukturę B używającą A. Gdy robisz to się rozwija na #ifndef A_HPP #define A_HPP
#ifndef B_HPP #define B_HPP
#ifndef A_HPP
#endif
struct B { A * b; }; #endif
struct A { B * b; }; #endif
Efektywnie masz problem jajka i kury. Po to masz deklaracje klas i funkcji, by można było ich używać zanim pojawią się w kodzie ich definicje (o ile w ogóle się pojawią). Typ który jest tylko zadeklarowany jest niekompletny - możesz go użyć tylko by utworzyć na niego wskaźnik lub referencję. |
|
multim Temat założony przez niniejszego użytkownika |
» 2020-01-23 09:26:19 Aktualnie mam takie pliki: Map.hpp: (dołącza tylko Enemy, które zawiera reszte includów) #ifndef WALL_MAP_HPP #define WALL_MAP_HPP
#include "Enemy.hpp"
namespace Tanks { class Map { private: std::vector < Enemy > enemies; }; }
#endif
Enemy.hpp (dołącza Tank...) #ifndef ENEMY_HPP #define ENEMY_HPP
#include "Tank.hpp"
namespace Tanks { class Enemy : public Tank { }; }
#endif
i Tank.hpp (korzysta z deklaracji zamiast includów) #ifndef TANK_OBJ_HPP #define TANK_OBJ_HPP
#include <vector> #include "Projectile.hpp"
namespace Tanks { class Map; class Tank : public GameCore::InteractiveObject { private: std::vector < Projectile > bullets; public: virtual void update(); }; }
#endif
W tym punkcie pojawia się problem ponieważ nie można używać wskaźników do niekompletnych typów (w metodzie update() chciałem wywołać map->collide(this, direction)). Czyli jedynym rozwiązaniem problemu, takiego że klasa Map zawiera obiekty typu Enemy, a klasa Enemy ma wskaźnik do Map jest zmiana architektury? |
|
pekfos |
» 2020-01-23 17:57:24 Implementacja jest pewnie w pliku .cpp, a tam nie ma już powodu mieć niekompletnych typów - dołącz wszystkie nagłówki jakich potrzeba. |
|
multim Temat założony przez niniejszego użytkownika |
» 2020-01-24 08:51:42 Implementacja metod wszystkich klas jest oczywiście w plikach *.cpp, ale problem jest z kolejnością dołączania (w momencie kiedy chcę odwołać się do Map w klasie Tank, Map jeszcze nie zostało dołączone). Przykład mojego toku rozumowania: (może być oczywiście błędny) main { #include "Map.hpp" { #include "Enemy.hpp" { #include "Tank.hpp" { class Map; } } } }
Błąd rzucany przez kompilator: src / Game / Tanks / Enemy.cpp src / Game / Tanks / Map.cpp src / Game / Tanks / Tank.cpp src / Game / Tanks / Tanks.cpp src / Main.cpp src / Game / Tanks / Tank.cpp: In member function 'virtual void Tanks::Tank::update(bool)': src / Game / Tanks / Tank.cpp: 42: 5: error: invalid use of incomplete type 'class Tanks::Map' map->update(); ^ ~ In file included from src / Game / Tanks / Tank.hpp: 5: 0, from src / Game / Tanks / Tank.cpp: 1: src / Game / Tanks / Projectile.hpp: 8: 8: note: forward declaration of 'class Tanks::Map' class Map; ^ ~~ src / Game / Tanks / Tank.cpp: 42: 7: error: invalid use of incomplete type 'class Tanks::Map' map->update(); ^ ~~~~~ In file included from src / Game / Tanks / Tank.hpp: 5: 0, from src / Game / Tanks / Tank.cpp: 1: src / Game / Tanks / Projectile.hpp: 8: 8: note: forward declaration of 'class Tanks::Map' class Map; ^ ~~
Jeżeli odwrócę kolejność dołączania (Map jest dołączone w Tank, a w Map zadeklarowane Enemy, Tank) Enemy/Tank jest niekompletnym typem i wracamy do punktu wyjścia. Równolegle przerzuciłem odpowiedzialność z metody update() Tank do Map i rozwiązało to problem dołączeń, ale mimo to byłbym usatysfakcjonowany gdybym się dowiedział, czy takie wzajemne odwołania do klas to ograniczenie języka, czy braki w jego poznaniu przeze mnie. Na co dzień programuję w Javie (gdzie programista nie przejmuje się kolejnością dołączania plików + jest kompilowany do kodu bajtowe) więc to dla mnie zupełnie inny świat i myślenie też inne ;) Nie chcę też zajmować specjalnie dużo Twojego czasu, najwyżej jak będę miał kiedyś więcej czasu, to poeksperymentuję jeszcze. |
|
pekfos |
» 2020-01-24 17:30:13 Dołącz wszystkie nagłówki jakich potrzeba do pliku .cpp. |
|
« 1 » 2 |