Ein Array kreieren, das verschiedene Klassen, die alle von X und Y erben, enthält



  • Hallo,
    ich habe eine Basisklasse X und jedes Objekt, das von der Basisklasse Y erbt, erbt auch von der Klasse X. Y und Z sind Klassen aus einer externen Bibliothek. . Wenn ich aber ein std::array deklariere, was soll der Typ sein? Wenn ich einen der beiden Typen als Typ angebe, so werden Methodenaufrufe danach als ungültig erklärt, weil ein der der Klasse X zugehörige Objekt natürlich keine Methoden der Klasse Y ausführen kann. Außerdem kann ich keine Klasse erstellen, die beide Klassen kombiniert, weil Ich nie die Klasse Y direkt nutze sondern nur Klassen, die von Erben von Y erben. Ich habe also viele verschiedene andere Klassen, die von X und Erben von Y erben. Ich möchte den Compiler sagen, dass jedes Objekt, das übergeben wird immer von X und Y erbt.
    Beispiel:

    class X
    {
        void methodeX();
    };
    
    class Y
    {
        void methodeY();
    };
    
    class Z : public Y
    {};
    
    class A : public Z public X
    {};
    
    class B : public Z public X
    {};
    
    std::vector<Objekt das A oder B sein kann*> vector;
    for (Objekt das A oder B sein kann* : vector)
    {
        (*Objekt).methodeX();
        (*Objekt).methodeY();
    }
    

    Das untere ließe sich mit auto lösen, aber ich kann ja kein array des typs auto deklarieren. (auto würde außerdem nicht ganz den Kern treffen, denn ich weiß ja, dass das Objekt immer von X und Y erbt)

    Ich weiß nicht, ob sich das ansatzweise sinnvoll oder verständlich anhört, aber Danke für alle Antworten



  • Generell würde ich sagen, dass du das Thema Polymorphie noch nicht ganz verstanden hast.
    In deinem konkreten Fall würde ich die Klasse Z von X und Y erben lassen, und alle weiteren A,B,... dann jeweils von Z.



  • Kannst du denn keine Basisklasse dafür anlegen und davon erben lassen?

    class Base : public Z, public X
    {
    };
    
    class A : public Base
    {
    };
    
    class B : public Base
    {
    };
    

    So kannst du dann einen std::vector<Base*> dafür benutzen (oder mittels Smartpointer).



  • std::variant und std::visit?

    Ich weiß nicht, ob sich das ansatzweise sinnvoll ...

    Hört sich merkwürdig an.



  • @axels

    1. Z ist eine Klasse aus einer Externen Bibliothek
    2. Exakt das habe ich gemacht, nur mit der Klasse Z dazwischen. A und B erben ja schon von X und Y, nur zwischen Y und A liegt halt noch Z. Aber dass alle von X und Y erben ist ja nicht das Problem, sondern wie ich dem Array das sage. Z.B Player stammt von einem Erbe von Drawable, nämlich Circle und von RenderObject ab. Enemy dagegen stammt von Rectangle, welches auch von Drawable erbt, und RenderObject ab. Ich kann also keine Klasse kreieren, die RenderObject und Drawable vereint, weil ich gar nicht selber von Drawable erbe, sondern nur von Erben von Drawable, die alle verschieden sind.
      Vieleicht ist das Übersichtlicher: https://ibb.co/SxR5zHh


  • @Th69 Das geht nicht, denn es gibt nicht nur Z als Erbende Klasse von X. Um genauer zu sein, ist Y die Klasse Drawable und es gibt 'zig völlig verschiedene externe Klassen, die davon erben, wie z.B Rectangle oder Text oder Circle. An denen kann ich nichts verändern. Ich habe meine eigene Klasse RenderObject, von der alle meine eigenen Klassen, wie Player oder Enemy erben sollen. Aber halt auch von einer der Klassen, die von Drawable abstammen. Und jetzt brauche ich einen Container für Objekte, die sowohl vom externen Drawable als auch von internen Klassen erben und sonst keinen gemeinsamen Nenner haben



  • Passt, ich muss X von Y erben lassen und das array als Typ Y deklarieren. So wird halt doppelt geerbt, was mit seinen Nachteilen kommt, aber wenn es nicht anders geht...



  • Das hört (bzw. liest) sich nach einem Design-Fehler an.

    Die doppelte Vererbung von Y bedeutet aber auch, daß es zwei Y-Basisklassenobjekte gibt und du dann mittels X auf andere Daten zugreifst als der Zugriff über Z. Du müßtest, wenn schon, dann virtualvererben (aber das geht nur, wenn du auch den Source von den verschiedenen Z d.h. von Drawable abgeleiteten Klassen, ändern könntest).
    Alternativ könntest du noch std::vector<X*> benutzen und per dynamic_cast<Y*> auf Y zugreifen (oder umgekehrt).

    Gerade aber bei Spielen (oder spiele-artigen Projekten) sollte man Komposition anstatt Vererbung (für z.B. Player oder Enemy etc.) benutzen, s.a. Entity component system.

    Edit: Hier noch zwei weitere (englischsprachige) Links (u.a. mit Diagrammen dazu):



  • Das klingt für mich komisch. Vlt ist ein Vector in dem Player und Enemy zusammen vorkommen können, einfach die falsche Datenstruktur.

    Wenn du drüber iterieren willst, um gleiche Funktionen aus einer gemeinsamen Basisklasse aufrufen zu können, könntest du auch direkt einen Pointer von der nehmen.

    Benötigst du die Vererbung tatsächlich zur Runtime? Sonst ist vielleicht "Compile Time Polymorphism" via Templates die richtige Lösung für dich.
    Hier:
    https://web.alcf.anl.gov/~zippy/publications/presentations/CompileTimePolymorphism/IBMWatsonTalk.pdf (ich habe nicht alles gelesen, aber die Funktionsweise von Compiletime Polymorphy passt)

    oder hier:
    https://www.c-plusplus.net/forum/topic/325513/compiletime-polymorphism



  • @daniel sagte in Ein Array kreieren, das verschiedene Klassen, die alle von X und Y erben, enthält:

    Ok, ich habe versucht da eine Lösung zu finden, und wundere mich über die schöne neue C++ Welt immer mehr. Man erwartet, doch, dass es mit shared_ptr möglich sein sollte den altmodischen Code darunter zu ersetzen. Irgend wie funktioniert das nicht. Weiß jemand weshalb der dynamic_pointer_cast nicht funktioniert wie er soll?

    #include <vector>
    #include <memory>
    #include <iostream>
    
    class X {
    public:
        void virtual methodeX() {
    		std::cout << "methodeX\n";
    	}
    };
    
    class Y {
    public:
        void virtual methodeY() {
    		std::cout << "methodeY\n";
    	}
    };
    
    class Z : virtual public Y
    {};
    
    class A : virtual public Z, virtual public X
    {};
    
    class B : virtual public Z, virtual public X
    {};
    
    using namespace std;
    
    int main () {
    	std::vector<shared_ptr<Y>> vy;
    
    	std::vector<Y*> v;
    	Z z;
    	A a;
    	B b;
    
    	v.push_back(&z);
    	v.push_back(&a);
    	v.push_back(&b);
    
    	vy.push_back(make_shared<Y>(Z{}));
    	vy.push_back(make_shared<Y>(A{}));
    	vy.push_back(make_shared<Y>(B{}));
    
    	for (auto sp: vy) {
    		sp->methodeY();
    
    		shared_ptr<X> xp = dynamic_pointer_cast<X>(sp);
    
    		X* p = xp.get();
    		cout << "p=" << p << "\n";
    
    		if (p) {
    			xp->methodeX();
    		}
    	}
    
    	cout << "\n";
    
    	for (size_t i = 0, e = v.size(); i != e; i++) {
    		v[i]->methodeY();
    
    		X* p = dynamic_cast<X*>(v[i]);
    
    		if (p) {
    			p->methodeX();
    		}
    	}
    }
    
    


  • @john-0 aus interesse: Was geht denn nicht, was gehen sollte? Schon gesehn...
    Ich würde die Nullpointer Überprüfung direkt auf dem Shared_ptr machen:

    if (xp != nullptr) {
       xp->methodeX();
    }
    


  • @Th69 Ja mit ECS habe ich mich beschäftigt, aber das ist mir derzeit noch zu anspruchsvoll



  • @john-0 Du fügst Ys ein.

    	vy.push_back(make_shared<Z>());
    	vy.push_back(make_shared<A>());
    	vy.push_back(make_shared<B>());
    


  • @manni66 sagte in Ein Array kreieren, das verschiedene Klassen, die alle von X und Y erben, enthält:

    @john-0 Du fügst Ys ein.

    Ja, und intuitiv ist es nicht, nicht den Basistyp zu verwenden. Ok, danke für den Hinweis auf das richtige Vorgehen.



  • @john-0 sagte in Ein Array kreieren, das verschiedene Klassen, die alle von X und Y erben, enthält:

    Ja, und intuitiv ist es nicht, nicht den Basistyp zu verwenden

    Eigentlich schon. Du ersetzt new durch make_shared. Man schreibt nicht new Y{Z{}} sondern new Zwenn man es einem Y* zuweisen will, weil man es richtig gelernt hat, nicht weil es intuitiv ist..



  • @manni66 sagte in Ein Array kreieren, das verschiedene Klassen, die alle von X und Y erben, enthält:

    Eigentlich schon. Du ersetzt new durch make_shared. Man schreibt nicht new Y{Z{}} sondern new Zwenn man es einem Y* zuweisen will, weil man es richtig gelernt hat, nicht weil es intuitiv ist.

    Hm, die Rolle von new Z übernimmt doch Z{} (man übergibt ein Objekt und keinen Zeiger mehr auf ein Objekt) und man will je einen SmartPointer von Typ Y haben. Mit Boost funktionierte das ganz noch so:

    boost::shared_ptr<Y> p (new Z);
    

    Deshalb meine Verwirrung. Ok, wieder etwas dazu gelernt, aber vieles vom neuen C++ ist nicht wirklich gut.



  • @john-0 sagte in Ein Array kreieren, das verschiedene Klassen, die alle von X und Y erben, enthält:

    Mit Boost funktionierte das ganz noch so:
    boost::shared_ptr<Y> p (new Z);

    Das geht mit std:: genauso, make_shared ist nicht zwingend. Es hat aber z.B. den Vorteil, dass die abgeleiteten Objekte hier korrekt gelöscht werden, obwohl der virtuelle Destruktor in den Basisklassen fehlt.



  • @john-0 sagte in Ein Array kreieren, das verschiedene Klassen, die alle von X und Y erben, enthält:

    Deshalb meine Verwirrung. Ok, wieder etwas dazu gelernt, aber vieles vom neuen C++ ist nicht wirklich gut.

    Was konkret ist denn nicht gut?

    Du musst std::make_shared ja auch mit boost::make_shared vergleichen und std::shared_ptr mit boost::shared_ptr.

    Hier ist ein Artikel, warum man das besser mit make_XXX statt XXX_ptr<...>(new ...) machen sollte: https://herbsutter.com/gotw/_102/


Log in to reply