Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?

Bład linkowania wielu deklaracji zapowiadających

Ostatnio zmodyfikowano 2020-01-27 14:28
Autor Wiadomość
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):
C/C++
#ifndef WALL_MAP_HPP
#define WALL_MAP_HPP

#include "..\Core\BasicObject.hpp"
#include "Enemy.hpp" // tu wcześniej był "Tank.hpp"
#include <vector>

namespace Tanks
{
    class Enemy; // problematyczna deklaracja zapowiadająca
    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
C/C++
#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
C/C++
#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").
P-176103
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.
P-176104
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ą.



P-176108
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
C/C++
#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
C/C++
#include "a.hpp"
to się rozwija na
C/C++
#ifndef A_HPP
#define A_HPP //// Zdefiniowane A_HPP

#ifndef B_HPP
#define B_HPP

#ifndef A_HPP //// Jeśli A_HPP jest niezdefiniowane. Jest, więc wyłączone z kompilacji
/*
#define A_HPP
#include "b.hpp"
struct A
{
B* b;
};
*/
#endif

struct B
{
    A * b; // Błąd: 'A' nieznane
};
#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ę.
P-176109
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)
C/C++
#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...)
C/C++
#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)
C/C++
#ifndef TANK_OBJ_HPP
#define TANK_OBJ_HPP

#include <vector>
#include "Projectile.hpp" // zawiera też deklarację "class Map;"

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?
P-176111
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.
P-176117
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)
C/C++
main
{
    #include "Map.hpp"
    {
        #include "Enemy.hpp"
        {
            #include "Tank.hpp"
            {
                class Map; // typ jest niekompletny, bo "koniec dołączenia" jest później
                // więc kompiler nie pozwala mi użyć metod dostępnych przez wskaźnik do obiektu typu Map
            }
        }
    } // koniec dołączenia Map
}

Błąd rzucany przez kompilator:
C/C++
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.
P-176121
pekfos
» 2020-01-24 17:30:13
Dołącz wszystkie nagłówki jakich potrzeba do pliku .cpp.
P-176125
« 1 » 2
  Strona 1 z 2 Następna strona