Ergebnis 1 bis 4 von 4

C++ Mit Zeigern vom Typ der Basisklasse Funktion mit Typ der abgeleiteten Klasse aufrufen

  1. #1 Zitieren
    Ritter Avatar von Feuerstern
    Registriert seit
    Sep 2007
    Beiträge
    1.798
    Hallo Leute
    ich habe in C++ zwei Zeiger auf Objekte die von der Klasse "Base" erben. Die Zeiger sind beide vom Typ Base, zeigen aber eigentlich auf Objekte vom Typ der abgeleiteten Klassen.
    Nun habe ich die Funktion printClassText welche einmal 2 Zeiger vom Typ "Base" entgegen nimmt und dann nochmal mit den Datentypen der abgeleiteten Klassen überladen:

    Code:
    class Base
    {
        public:
            virtual void print() const
            {
                std::cout << "Class Base" << std::endl;
            }
    };
    
    class ChildA : public Base
    {
        public:
            virtual void print() const
            {
                std::cout << "Class ChildA" << std::endl;
            }
    };
    
    class ChildB : public Base
    {
        public:
            virtual void print() const
            {
                std::cout << "Class ChildB" << std::endl;
            }
    };
    
    void printClassText(Base *a, Base *b)
    {
        std::cout << "Function parameters: Base Base" << std::endl;
    }
    
    void printClassText(ChildA *a, ChildB *b)
    {
        std::cout << "Function parameters: ChildA ChildB" << std::endl;
    }
    
    
    int main()
    {
        Base *a = new ChildA();
        Base *b = new ChildB();
        printClassText(a, b);
    
        delete a;
        delete b;
    
        return 0;
    }
    Wie erwartet wird mit printClassText(a, b) "Function parameters: Base Base" ausgegeben. Ich hätte aber gerne das die überladene Funktion aufgerufen wird und "Function parameters: ChildA ChildB" ausgibt.

    Gibt es da eine Möglichkeit?
    Hintergrund ist das ich in einem kleinen 2d Spiel ein Kollisions System einbauen möchte welches mit Kreisen und Rechtecken als Kollisions Typen umgehen kann. Dazu wollte ich dann den Entities als Attribut einen Zeiger vom Typ "Collision" geben vond er die Klassen "CollisionRect" und "CollisionCircle" abgeleitet sind und je nach Art der gewollten Kollisionsart auf eines der beiden Objekt Arten Zeigen lassen.
    Per Getter würde ich dann auf den Zeiger zugreifen und die Methode isColliding() aufrufen, welche mit den verschiedenen Kollisions Typen überladen ist:
    Code:
    bool isColliding(CollisionRect *a, CollisionRect *b);
    bool isColliding(CollisionCircle *a, CollisionCircle *b);
    bool isColliding(CollisionRect *a, CollisionCircle *b);
    bool isColliding(CollisionCircle *a, CollisionRect *b);
    Mir fallen da momentan nur Tests mit dynamic_casts ein und mehreren if Bedingungen oder isColliding als virtuelle Methode der Kollisions Klassen zu implementieren und dan per
    Visitor design pattern die richtige Methode aufzurufen (so wie es in der akzeptierten Antwort gemacht wurde):
    http://stackoverflow.com/questions/2...tection-system
    Aber ob die Herangehensweisen so optimal wären?

    Grüße
    Feuerstern
    Feuerstern ist offline

  2. #2 Zitieren
    Dea
    Registriert seit
    Jul 2007
    Beiträge
    10.446
    Was spricht denn gegen

    Code:
    void printClassText(Base *a, Base *b)
    {
        std::cout << "Function parameters: ";
        a->print(); b->print();
    }
    ?
    Lehona ist offline

  3. #3 Zitieren
    Ritter Avatar von Feuerstern
    Registriert seit
    Sep 2007
    Beiträge
    1.798
    Danke für deine Antwort.
    Das ganze sollte nur ein minimal Beispiel sein, es geht nicht direkt um die Ausgabe in print(), die Methode hatte ich zum Testen implementiert.
    In der späteren Umsetzung für die Kollision sollen ChildA und ChildB noch zusätzliche Methoden und Attribute besitzen die ich dann in der Funktion isColliding (Im Minimal Beispiel printClassText) benutzen möchte. Die Objekt befinden sich alle Zusammen in einem Vektor der dann Zeiger vom Typ Base speichern kann (Von denen die Objekte hinter dem Zeiger aber alle entweder vom Typ ChildA oder ChildB sind).
    Code:
    std::vector<Base*> objekte;
    Base *a = new ChildA();
    Base *b = new ChildB();
    objekte.push_back(a);
    objekte.push_back(b);
    for (Base *objektA : objekte)
    {
        for (Base *objektB : objekte)
       {
           // Ruft immer printClassText(Base, Base) auf
           // Wenn ObjektA nun aber eigentlich auf ein Objekt vom Typ ChildA zeigt und ObjektB auf ChildB hätte ich gerne das die Funktion printClassText(ChildA, ChildB); aufgerufen wird
           printClassText(objektA, objektB);
       }
    }
    Ich hoffe mein 2. Beispiel (baut auf das Beispiel von oben auf) verdeutlicht es etwas besser.

    Grüße
    Feuerstern
    Feuerstern ist offline

  4. #4 Zitieren

    Batmanistrator
    Avatar von Thoronador
    Registriert seit
    Jul 2005
    Ort
    Morrowind, Vvardenfell-Distrikt
    Beiträge
    20.405
    Zitat Zitat von Feuerstern Beitrag anzeigen
    Die Objekt befinden sich alle Zusammen in einem Vektor der dann Zeiger vom Typ Base speichern kann (Von denen die Objekte hinter dem Zeiger aber alle entweder vom Typ ChildA oder ChildB sind).
    Nur als Hinweis: Wenn man später die Zeiger auf die Objekte aus dem std::vector entfernt und nicht mehr benötigt, dann muss man diese später auch wieder mit delete freigeben, sonst schafft man sich ein Speicherleck. Aussehen müsste das dann in etwa so:

    Code:
    std::vector<Base*> objekte;
    Base *a = new ChildA();
    Base *b = new ChildB();
    objekte.push_back(a);
    objekte.push_back(b);
    
    /* Mache hier irgendwas mit dem Vektor. */
    
    //Vektor leeren und Objekte freigeben
    while (!objekte.empty())
    {
      Base p = objekte.back();
      objekte.pop_back();
      delete p;
    } //end of while
    Das sieht erst einmal einfach aus, und bei kurzen Programmen ist das auch noch übersichtlich genug, um das Freigeben der Objekte nicht zu vergessen. Natürlich wird das deutlich komplizierter, wenn man den std::vector mit den Zeigern an mehreren Stellen nutzt und nicht einfach nur starr zwei Objekte erzeugt und in den std::vector schmeißt, sondern wenn man das dynamisch erledigt und ggf. mit hunderten von Objekten, bei denen nicht von vornherein klar ist, wie lange sie im std::vector verweilen werden. Daher rate ich dir hier zur Nutzung von sogenannten Smart Pointern. Diese sind seit C++11 verfügbar und kümmern sich selbst darum, dass das von ihnen verwaltete Objekt wieder freigegeben wird. Eine Möglichkeit wäre in deinem Fall also std::unique_ptr. Hier mal ein Beispiel mit std::unique_ptr. Der Code für die Klassen ist im Wesentlichen nur dazu da, um zu sehen, wann welche Methode aufgerufen wird:

    Code:
    #include <iostream>
    #include <memory>
    #include <vector>
    
    class Base
    {
      public:
        Base()
        {
          std::cout << "creating instance of Base" << std::endl;
        }
        virtual ~Base()
        {
          std::cout << "destroying instance of Base" << std::endl;
        }
        virtual void hello()
        {
          std::cout << "Hello from Base." << std::endl;
        }
    };
    
    class ChildA : public Base
    {
      public:
        ChildA()
        {
          std::cout << "creating instance of ChildA" << std::endl;
        }
        virtual ~ChildA()
        {
          std::cout << "destroying instance of ChildA" << std::endl;
        }
        virtual void hello()
        {
          std::cout << "Hello from ChildA." << std::endl;
        }
    };
    
    class ChildB : public Base
    {
      public:
        ChildB()
        {
          std::cout << "creating instance of ChildB" << std::endl;
        }
        virtual ~ChildB()
        {
          std::cout << "destroying instance of ChildB" << std::endl;
        }
        virtual void hello()
        {
          std::cout << "Hello from ChildB." << std::endl;
        }
    };
    
    /* C++11 contains no make_unique(), but C++14 does, so we create our own
       make_unique as workaround. */
    template<typename T, typename ...Args>
    std::unique_ptr<T> make_unique(Args&& ...args)
    {
      return std::unique_ptr<T>( new T( std::forward<Args>(args)... ) );
    }
    
    int main()
    {
      std::vector<std::unique_ptr<Base> > objects;
      objects.push_back(make_unique<ChildA>());
      objects.push_back(make_unique<ChildB>());
    
      for(auto & i : objects)
      {
        i->hello();
      }
    
      return 0;
    }
    Ein explizites delete braucht's in diesem Falle nicht, das macht std::unique_ptr von allein. Die Ausgabe des Programms sieht dann so aus:
    Code:
    creating instance of Base
    creating instance of ChildA
    creating instance of Base
    creating instance of ChildB
    Hello from ChildA.
    Hello from ChildB.
    destroying instance of ChildA
    destroying instance of Base
    destroying instance of ChildB
    destroying instance of Base
    Wichtigster Punkt (oder einer der wichtigsten Punkte) dabei ist, dass der Destruktor von Base virtuell ist, damit auch die Destruktoren der abgeleiteten Klassen aufgerufen werden, wenn diese zerstört/freigegeben werden. Im obigen Beispiel ist das nicht so wichtig, aber wenn die Destruktoren der abgeleiteten Klassen irgendwann mal Ressourcen freigeben (z. B. Dateihandles schließen o. ä.), dann wird das ohne virtual ~Base() ein Problem, denn dann sähe die Ausgabe so aus:
    Code:
    creating instance of Base
    creating instance of ChildA
    creating instance of Base
    creating instance of ChildB
    Hello from ChildA.
    Hello from ChildB.
    destroying instance of Base
    destroying instance of Base
    Wie man sieht, werden die Destruktoren von ChildA und ChildB in dem Falle nicht aufgerufen, was natürlich nicht das gewünschte Verhalten ist.
    Thoronador ist offline

Berechtigungen

  • Neue Themen erstellen: Nein
  • Themen beantworten: Nein
  • Anhänge hochladen: Nein
  • Beiträge bearbeiten: Nein
  •