Vererbung von abstrakter Klasse und Typ



  • Hi! Wenn ich den folgenden code habe

    #include <vector>
    
    class Animal {
    public:
    	virtual void makeNoise() = 0;
    };
    
    class Cat : public Animal {
    	void makeNoise() { /*Miao*/ }
    	void burrowPoop() { }	
    };
    
    class Dog : public Animal {
    	void makeNoise() { /*Woof*/ }
    	void biteBone() { }
    };
    
    int main(int argc, char* argv[]) {
    	std::vector<Animal*> pets;
    	pets.push_back(new Dog());
    	pets.push_back(new Cat());
    
    	for (auto nextPet : pets) {
    		nextPet->makeNoise();
    	}
    
    	//I want to call burrowPoop() for all Cats within pets
    
    	//Clean up
    	for (auto nextPet : pets) delete nextPet;
    	return 0;
    }
    

    Wie kann ich dann burrowPoop() für alle Katzen aufrufen?



  • Das geht mit dynamic_cast. Das liefert nullptr, wenn der Typ nicht passt.

    for (auto pet : pets) {
       auto cat = dynamic_cast<Cat*>(pet);
       if (cat) cat->burrowPoop();
    }
    

    Zusätzlich stelle ich mir die Frage, ob das hier nötig ist. Ich finde es sehr unschön, durch diese Lösung wieder einen speziellen Typ herauszufiltern. Zumindest sehr unschön, wenn das mit mehreren Typen und/oder an mehreren Stellen auftritt. Dann gibt es ggf. eine schöndere Lösung.

    Abgesehen davon würde ich noch vorschlagen, Smartpointer zu verwenden, d.h. für deinen Tiervector einen vector von unique_ptr zu nehmen:

    std::vector<std::unique_ptr<Animal>> pets;
    


  • Ich schlage noch vor einen virtuellen dtor zu ergänzen

    virtual ~Animal() = default;
    

    und die überschriebenen virtuellen Methoden mit override zu kennzeichnen.



  • Wie kann ich dann burrowPoop() für alle Katzen aufrufen?

    in dem du eine Liste mit allen Katzen / katzenähnlichen Wesen führst, und die beim erzeugen mit befüllst

    du an der Klasse die Info Pflegst (Typ, enum ... ) ob es ein hund oder katze oder affe ist.
    Das ist als Design üblich und ok.
    Nicht mehr ok ist, die info dann zu verwenden um den downcast zu machen.

    Ciao ...



  • wob schrieb:

    Ich finde es sehr unschön, durch diese Lösung wieder einen speziellen Typ herauszufiltern. Zumindest sehr unschön, wenn das mit mehreren Typen und/oder an mehreren Stellen auftritt. Dann gibt es ggf. eine schöndere Lösung.

    Warum und an welche schöneren Lösungen denkst du?

    RHBaum schrieb:

    in dem du eine Liste mit allen Katzen / katzenähnlichen Wesen führst, und die beim erzeugen mit befüllst

    Wenn es aber eine Liste "Zoo" sein soll, die alle Tiere aufnehmen kann? Das finde ich nicht so unrealistisch.

    Interessiert mich wirklich, weil ich auch schon so was ähnliches vorhatte (jetzt keine Tiere, aber im Prinzip).



  • Das Besuchermuster kommt auch noch in Betracht:

    class Cat;
    class Dog;
    
    struct IAnimalVisitor
    {
       IAnimalVisitor() 
       {
       };
    
       virtual ~IAnimalVisitor() 
       {
       }
    
       virtual void visit( Cat& c ) = 0;
       virtual void visit( Dog& d ) = 0;
    };
    
    class Animal
    {
       ...
    public:
      Animal()
      {
      }
    
      virtual ~Animal()
      {
      }
    
      virtual void accept( IAnimalVisitor& Visitor ) = 0;
    };
    
    class Cat : public Animal
    {
       ...
    public:
       ...
       void accept( IAnimalVisitor& Visitor )
       {
          Visitor.visit( *this );
       }
    
       void burrowPoop()
       {
       }
    };
    
    class Dog : public Animal
    {
       ...
    public:
       ...
       void accept( IAnimalVisitor& Visitor )
       {
          Visitor.visit( *this );
       }
    };
    
    struct CatBurrowPoopVisitor : public IAnimalVisitor
    {
       void visit( Cat& c )
       {
          c.burrowPoop();
       }
    
       void visit( Dog& d )
       { // nix zu tun
       }
    };
    
    int main(int argc, char* argv[]) {
        std::vector<Animal*> pets;
        pets.push_back(new Dog());
        pets.push_back(new Cat());
    
        for (auto nextPet : pets) {
            nextPet->makeNoise();
        }
    
        //I want to call burrowPoop() for all Cats within pets
        CatBurrowPoopVisitor cbpv;
        for (auto nextPet : pets) {
           nextPet->accept( cbpv );
        }
    
        //Clean up
        for (auto nextPet : pets) delete nextPet;
        return 0;
    }
    


  • temi schrieb:

    wob schrieb:

    Ich finde es sehr unschön, durch diese Lösung wieder einen speziellen Typ herauszufiltern. Zumindest sehr unschön, wenn das mit mehreren Typen und/oder an mehreren Stellen auftritt. Dann gibt es ggf. eine schöndere Lösung.

    Warum und an welche schöneren Lösungen denkst du?

    Ich habe erstmal an gar keine Lösung gedacht, weil das sicher vom genauen Fall abhängt. Aber das ganze ist unter anderem deswegen unschön/schlecht, weil es versteckt irgendwo vorkommen kann. Stell dir vor, du hast da irgendwo ne Fallunterscheidung:

    if (dynamic_cast<Giraffe*>(animal)) tuWasMitGiraffe(static_cast<Giraffe*>(animal));
    else if (dynamic_cast<Tiger*>(animal)) tuWasMitTiger(static_cast<Tiger*>(animal));
    else if ...
    

    Das ist doch furchtbar.

    Noch schlimmer: man tendiert dann dazu, solchen Code noch an mehreren Stellen einzubauen. Das wird dann schnell sehr sehr unübersichtlich. Versuch es doch mal selbst. Das wirst du dir sehr schnell wieder abgewöhnen!

    Ob es nun ein Visitor sein muss oder eine ganz andere Lösung sinnvoll ist (im simpelsten Fall separates Speichern der Dogs oder implementieren einer leeren Funktion virtual void burrowPoop(){} in Animal), ist so ohne weiteres schlecht zu sagen.


Anmelden zum Antworten