Anfängerproblem: Klassen benötigen gegenseitig eine Deklaration der jeweils Anderen



  • Sorry, aber ich bin noch ein ziemlicher Anfänger. Referenzen versteh ich, nutze ich ja auch schon:

    Die kleinere Funktion:

    void Kollisionsabfrage(Ball &Ball, Spieler &Spieler);
    

    Aber wieso Referenzen auf Konstanten? Ich ändere die Werte der Objekte, da wären Konstanten ja genau das falsche oder weiß ich da was nicht? 😕

    Und mit Forward-Deklarationen meinst du einfach, dass ich dann die beiden Klassen ganz normal nacheinander bzw. unabhängig voneinander Deklarieren kann?

    Danke schonmal für die Hilfe 🙂



  • Little Programmer schrieb:

    Sorry, aber ich bin noch ein ziemlicher Anfänger. Referenzen versteh ich, nutze ich ja auch schon:

    Die kleinere Funktion:

    void Kollisionsabfrage(Ball &Ball, Spieler &Spieler);
    

    Aber wieso Referenzen auf Konstanten? Ich ändere die Werte der Objekte, da wären Konstanten ja genau das falsche oder weiß ich da was nicht? 😕

    Und mit Forward-Deklarationen meinst du einfach, dass ich dann die beiden Klassen ganz normal nacheinander bzw. unabhängig voneinander Deklarieren kann?

    Danke schonmal für die Hilfe 🙂

    Wenn du die Objekte änderst, kannst du natürlich kein const nehmen. Forward Deklaration bedeutet einfach, dass du beispielsweise folgendes machst:

    Header:

    void Kollisionsabfrage(class Ball& ball, class Spieler& spieler);
    

    Cpp:

    #include "Header.h"
    #include "Ball.h"
    #include "Spieler.h"
    
    void Kollisionsabfrage(class Ball& ball, class Spieler& spieler)
    {
     // ...
    }
    


  • Üblicher so:
    Header:

    class Ball;
    class Spieler;
    void Kollisionsabfrage(Ball& ball,Spieler& spieler);
    


  • Ah, das ist mir neu. Vielen Dank für eure Hilfe. Also mit

    class Klassenname;
    

    Kann ich einfach einen Klassennamen deklarieren, damit ich diese Klasse als Argument verwenden kann?

    Nun beschwert sich der Compiler aber, dass alle Variablen protected sind, obwohl die Funktion ja friend der beiden Klassen ist:

    C:\...\Ball.h|27|error: `double Ball::dPosX' is protected|
    C:\...\Kollisionsabfrage.cpp|9|error: within this context|
    etc.
    

    EDIT: Ich hab solange gegoogled, bis ich wieder hier bei diesem Thema rauskam. Laut Google müsste es funktionieren. Irgendwie liest mein Compiler anscheinend erst die Funktion und erst dann die Klassen und weiß deshalb zum Zeitpunkt an dem er an der Funktion ist noch nicht, dass diese ein friend der Klassen ist. Kann das sein?



  • Ich habe eine mögliche Lösung hier gefunden:

    http://www.idevgames.com/forum/archive/index.php/t-11562.html

    Im letzten Post steht, dass es helfen würde, die klassen und ihre friend-Funktion in den selben namespace zu schieben und diesen auch in der friend-Deklaration zu verwenden. Ich habe den Namespace Game erstellt, aber dadurch hat sich nichts geändert. Weiterhin ignoriert der Compiler fröhlich die friend-Anweisungen.



  • Ich stelle mal alle Header und die Funktion an sich hier rein, vielleicht sieht ja jemand den Fehler.

    Ball.h

    namespace Game
    {
        class Ball
        {
            class Spieler;
            class SDL_Surface;
    
            friend void Game::Kollisionsabfrage(Ball &Ball, Spieler &Spieler);
            friend void Game::Kollisionsabfrage(SDL_Surface *screen, Spieler &Spieler1,
                                                  Spieler &Spieler2, Ball &Ball);
    
            public:
                Ball(int* SpielfeldversatzX, int* SpielfeldX, int* SpielfeldversatzY,
                     int* SpielfeldY);
                void Reset(int* SpielfeldversatzX, int* SpielfeldX,
                           int* SpielfeldversatzY, int* SpielfeldY,
                           int Aufschlagspieler);
                void PositionBerechnen();
                void SpeedCheck();
                void Abdecken(SDL_Surface *screen);
                void Darstellen(SDL_Surface *screen);
    
            protected:
                double dPosX;
                double dPosY;
                int w;
                int h;
                double dSpeedX;
                double dSpeedY;
        };
    }
    

    Spieler.h

    namespace Game
    {
        class Spieler
        {
            class Ball;
            class SDL_Surface;
    
            friend void Game::Kollisionsabfrage(Ball &Ball, Spieler &Spieler);
            friend void Game::Kollisionsabfrage(SDL_Surface *screen, Spieler &Spieler1,
                                          Spieler &Spieler2, Ball &Ball);
    
            public:
                Spieler(int* SpielfeldversatzX, int* SpielfeldX, int* SpielfeldversatzY,
                        int* SpielfeldY, int AILevel);
                void SpielerSteuerung(bool *laeuft, int* SpielfeldX,
                                      int* SpielfeldversatzY, int* SpielfeldY);
                void PositionBerechnen();
                void Abdecken(SDL_Surface *screen);
                void Darstellen(SDL_Surface *screen);
    
            protected:
                double dPosX;
                double dPosY;
                int w;
                int h;
                int Punkte;
                int AILevel; //0 bedeutet es ist ein vom Spieler gesteuerter Spieler
                double dSpeedY;
        };
    }
    

    Kollisionsabfrage.h

    #ifndef _BALL_H_
    #define _BALL_H_
    #include "Ball.h"
    #endif
    
    #ifndef _SPIELER_H_
    #define _SPIELER_H_
    #include "Spieler.h"
    #endif
    
    #ifndef _SDL_H_
    #define _SDL_H_
    #include "C:/Programme/CodeBlocks/SDL-1.2.13/include/SDL.h"
    #endif
    
    class Ball;
    class Spieler;
    
    namespace Game
    {
        void Kollisionsabfrage(Ball &Ball, Spieler &Spieler);
    
        void Kollisionsabfrage(SDL_Surface *screen, Spieler &Spieler1,
                               Spieler &Spieler2, Ball &Ball);
    }
    

    Und Kollisionsabfrage.cpp:

    #ifndef _KOLLISIONSABFRAGE_H_
    #define _KOLLISIONSABFRAGE_H_
    #include "Kollisionsabfrage.h"
    #endif
    
    void Game::Kollisionsabfrage(Ball &Ball, Spieler &Spieler)
    {
        //Kontrollieren ob Ball Spieler von der Seite überschneiden würde
        if ((Ball.dPosX <= (Spieler.dPosX + Spieler.w)) &&
           ((Ball.dPosX + Ball.w) >= Spieler.dPosX))
        {
            //Kontrollieren ob Ball Spieler von O./U. überschneiden würde
            if ((Ball.dPosY <= (Spieler.dPosY + Spieler.h)) &&
               ((Ball.dPosY + Ball.h) >= Spieler.dPosY))
            {
                //Geschwindigkeit ändern
                Ball.dSpeedY += Spieler.dSpeedY;
                Ball.dSpeedX = - (Ball.dSpeedX + Spieler.dSpeedY/2 ) / 2;
    
                //Position ändern, damit nicht überschneiden
                if ((Ball.dPosX <= (Spieler.dPosX + Spieler.w)/2) &&
                   (Ball.dPosX >= Spieler.dPosX))
                {
                    Ball.dPosX = Spieler.dPosX - Ball.w - 1;
                }
    
                if ((Ball.dPosX >= (Spieler.dPosX + Spieler.w)/2) &&
                   (Ball.dPosX <= (Spieler.dPosX + Spieler.w)))
                {
                    Ball.dPosX = Spieler.dPosX + Spieler.w + 1;
                }
    
                if ((Ball.dPosY <= (Spieler.dPosY + Spieler.h)/2) &&
                   (Ball.dPosY >= Spieler.dPosY))
                {
                    Ball.dPosY = Spieler.dPosY - Ball.h - 1;
                }
    
                if ((Ball.dPosY >= (Spieler.dPosY + Spieler.h)/2) &&
                   (Ball.dPosY <= (Spieler.dPosY + Spieler.h)))
                {
                    Ball.dPosY = Spieler.dPosY + Spieler.h + 1;
                }
            }
        }
    }
    
    void Kollisionsabfrage(SDL_Surface *screen, Spieler &Spieler1,
                           Spieler &Spieler2, Ball &Ball)
    {
        if (Ball.dPosX <= 0)
        {
            Spieler2.Punkte++;
            Ball::Reset();
        }
    
        if (Ball.dPosY <= 0)
        {
            Ball.dSpeedY = -Ball.dSpeedY;
            Ball.dPosY = 1;
        }
    
        if ((Ball.dPosX + Ball.w) >= (screen->w))
        {
            Spieler1.Punkte++;
            Ball::Reset();
        }
    
        if ((Ball.dPosY + Ball.h) >= (screen->h))
        {
            Ball.dSpeedY = -Ball.dSpeedY;
            Ball.dPosY = (screen->h - 1);
        }
    }
    

    Sieht jemand einen Grund, warum Kollisionsabfrage nicht als friend von Ball und Spieler gesehen wird? Notfalls müsste ich die Klassen Ball und Spieler um Funktionen für jede einzelne Variable ergänzen, die diese jeweils ausgeben. Aber das wäre ziemlich unschön. 😞



  • Normalerweise kommen Header Guards in den Header und nicht in die .cpp.. 🙄



  • Die Forward-Declarations dürfen nciht innerhalb der Klasse stehen, damit deklarierst du Unterklassen Spieler::Ball bzw. Ball::Spieler. Die friend-deklarationen beziehen sich dann automatisch auf diese Unterklassen, so dass z.B.
    [c]void Game::Kollisionsabfrage(Ball &Ball, **Bal::**Spieler &Spieler);[/c] ein friend von Ball wäre. Pack die Forward-Deklarations in den Scope wo sie hingehören, d.h. außerhalb der Klassen, innerhalb des Namespace:

    namespace Game
    {
        class Spieler; //Fwd-Decl
        class SDL_Surface; //Fwd-Decl;
    
        class Ball
        {
    
            friend void Game::Kollisionsabfrage(Ball &Ball, Spieler &Spieler);
    //...
    


  • Ich komme nochmal auf mein "Abhängigkeiten reduzieren"-Punkt. Die Kollisionsabfrage bei Dir greift direkt auf geschützte Objekt-Elemente von Ball und Spieler zu. Das ist schlecht. Du solltest dafür Funktionen zur Verfügung stellen, die man in kollisionsabfrage() einfach aufrufen kann. Damit ersparst Du Dir auch die sonst nötigen friend-Deklarationen.

    Eventuell kannst Du die Objekteigenschaften, die Du in Kollisionsabfrage benutzt, abstrahieren. ZB so:

    struct Vec2d
    {
      double xy[2];
    
      explicit Vec2d(double x=0, double y=0)
      { xy[0]=x; xy[1] = y; }
    
      double&       x()       {return xy[0];}
      double const& x() const {return xy[0];}
      double&       y()       {return xy[1];}
      double const& y() const {return xy[1];}
    };
    
    class Objekt2D
    {
    protected:
      ~Objekt2D() {} // alternativ: public und virtuell
    public:
      Vec2d position() const = 0;
      Vec2d groesse() const = 0;
      Vec2d geschwindigkeit() const = 0;
      void position(Vec2d p) = 0;
      void geschwindigkeit(Vec2d p) = 0;
    };
    
    class Ball : public Objekt2D
    {
      ...
    };
    
    class Spieler : public Objekt2D
    {
      ...
    };
    
    void kollisionsabfrage(Objekt2D & o1, Objekt2D & o2)
    {
      ...
    }
    

    ...ist ja nur ein Vorschlag.

    Gruß,
    SP



  • class Objekt2D
    {
    protected:
      ~Objekt2D() {} // alternativ: public und virtuell
    public:
      Vec2d position() const = 0;
      Vec2d groesse() const = 0;
      Vec2d geschwindigkeit() const = 0;
      void position(Vec2d p) = 0;
      void geschwindigkeit(Vec2d p) = 0;
    };
    

    Nachtrag: Vor jede Funktionsdeklaration im public-Bereich gehört noch ein "virtual".

    Diese Klassendefinition könntest Du in einen eigenen Header packen, der dann für die "Kollisionsabfrage"-Funktion eingebunden wird, so dass diese nichts von Spieler oder Ball wissen muss.

    Zumindest ist es erstrebenswert, die Programmteile weitestgehend zu entkoppeln.



  • Sebastian Pizer schrieb:

    struct Vec2d
    {
      double xy[2];
    

    Warum Du auch? Von Dir hätte ich

    struct Vec2d
    {
      double x;
      double y;
    

    erwartet.



  • Um ehrlich zu sein habe ich keine Ahnung, was:

    explicit Vec2d(double x=0, double y=0)
      { xy[0]=x; xy[1] = y; }
    
      double&       x()       {return xy[0];}
      double const& x() const {return xy[0];}
      double&       y()       {return xy[1];}
      double const& y() const {return xy[1];}
    

    macht.

    Vor jede Funktionsdeklaration im public-Bereich gehört noch ein "virtual".

    Ich brauch doch nur virtuelle Funktionen, wenn die Klasse erbt oder vererbt, oder?

    Aber euch allen schonmal vielen Dank für den Hinweis mit den Klassendeklarationen in den Klassen. 🙂



  • Nochmal ganz kurz zwei Sachen^^.

    pumuckles Problemlösung hat wieder ein neues Problem hervorgerufen:

    namespace Game
    {
        class Spieler; //Fwd-Decl
        class SDL_Surface; //Fwd-Decl;
    
        class Ball
        {
    
            friend void Game::Kollisionsabfrage(Ball &Ball, Spieler &Spieler);
    //...
    
    C:\...\Kollisionsabfrage.cpp|66|error: invalid use of undefined type `struct Game::SDL_Surface'|
    

    SDL_Surface darf nicht zum namespace Game gehören, sonst funktioniert es nicht mehr. Ich habs jetzt einfach außerhalb geschrieben und das geht.

    EDIT: Und das zweite Problem hat sich mir grad auch erschlossen. Nur irgendwie bin ich zu blöd, es richtig zu schreiben.

    Ich muss der Elementfunktion (heißt doch so, oder? 😕 ) Reset ein Objekt mitgeben. Ich wollte das so machen, wobei das Objekt auch Ball heißt:

    Ball::Ball.Reset(SpielfeldversatzX, SpielfeldX, SpielfeldversatzY,
                             SpielfeldY, 2);
    

    Der Compiler meint dazu nur:

    C:\...\Kollisionsabfrage.cpp|56|error: expected unqualified-id before '.' token|
    

    Nehme ich anstatt Ball irgendeinen anderen Begriff als Objektnamen, um den Compiler nicht zu verwirren 😉 , dann schreibt er stattdessen:

    C:\...\Kollisionsabfrage.cpp|56|error: `Kugel' is not a member of `Game::Ball'|
    

    Wie schreibe ich denn eine Elementfunktion im namespace so, dass der Compiler versteht, was ich meine?



  • Little Programmer schrieb:

    Ball::Ball.Reset(SpielfeldversatzX, SpielfeldX, SpielfeldversatzY,
                             SpielfeldY, 2);
    

    Gibt es den namespace Ball? Und was meinst du mit "ein Objekt mitgeben"?
    Wenn du ein Objekt einBall vom Typ Game::Ball hast, rufst du die Elementfunktion Reset so auf: einBall.Reset(...);

    Es ist übrigens eine schlechte Idee, Objekte wie ihre Klasse zu nennen, das führt nur zu Problemen.
    Besser so:

    Ball ball; //Objektnamen immer klein schreiben
    TBall Ball; //Borland-Art
    BG_Ball Ball; //Projektkürzel voranstellen
    


  • volkard schrieb:

    Sebastian Pizer schrieb:

    struct Vec2d
    {
      double xy[2];
    

    Warum Du auch? Von Dir hätte ich

    struct Vec2d
    {
      double x;
      double y;
    

    erwartet.

    *mit-den-Achseln-zuck*

    Es wär doch nett, wenn man die Komponenten per Index ansprechen könnte.

    Gruß,
    SP



  • Sebastian Pizer schrieb:

    Es wär doch nett, wenn man die Komponenten per Index ansprechen könnte.

    Einfach nur so, ohne einen Algorithmus im Sinn, wo man das brauchen könnte.



  • volkard schrieb:

    Sebastian Pizer schrieb:

    Es wär doch nett, wenn man die Komponenten per Index ansprechen könnte.

    Einfach nur so, ohne einen Algorithmus im Sinn, wo man das brauchen könnte.

    Einfach nur so, wissend dass es durchaus einen Algorithmus geben kann den man grade nicht im Sinn hat. Und dass es auf die Performance, den Speicherverbrauch und das Interface keinen Einfluss hat. Nur auf die Erweiterbarkeit eben.



  • pumuckl schrieb:

    Einfach nur so, wissend dass es durchaus einen Algorithmus geben kann den man grade nicht im Sinn hat.

    Dann mußt Du aber auch Nägel mit Köpfen machen und eine Fahrrad& getFahrrad() anbieten, einfach nur so, weil es einen Algorithmus geben könnte, der eun Fahrrad brauchen könnte. Würde ich nie machen.



  • volkard schrieb:

    Dann mußt Du aber auch Nägel mit Köpfen machen und eine Fahrrad& getFahrrad() anbieten, einfach nur so, weil es einen Algorithmus geben könnte, der eun Fahrrad brauchen könnte.

    Nein, im Gegensatz zu der Entscheidung 2 x double <-> double[2] wäre das ein Eingriff ins Interface der Klasse, der zu dem Zeitpunkt (noch) nicht nötig ist.


  • Administrator

    @pumuckl & Sebastian Pizer,
    Wieso nicht eher sowas?

    struct Vec2d
    {
      double x;
      double y;
    
      double& operator [](std::size_t index)
      {
        assert(index < 2);
        return (index == 0 ? x : y);
      }
    
      double operator [](std::size_t index) const
      {
        assert(index < 2);
        return (index == 0 ? x : y);
      }
    };
    

    Würde ich irgendwie als schöner empfinden 😉

    Grüssli


Anmelden zum Antworten