Wie überprüfen, zu welcher Oberklasse ein Objekt gehört?



  • Hallo,

    wie kann ich überprüfen, ob ein Objekt zu einer Klasse gehört, die sich von einer bestimmten Klasse ableitet?

    Also, angenommen ich habe eine Klassenheirarchie von 3 Klassen: Oberste Klasse ist A, von dieser leitet sich die Klasse B ab und von dieser wiederum die Klasse C. Wenn ich nun ein Array mit Pointern auf Objekte von A deklariert habe, in diesem Array aber faktisch Pointer von Subklassen von A (z.B. C) drinstehen, wie kann ich überprüfen, ob die Arrayelemente auf Objekte zeigen, die auch zu B gehören? Ist halt dann relevant, wenn B noch eine "Geschwisterklasse" hat, von der sich auch wieder Klassen ableiten.
    Kann man sowas auf einfache Art überhaupt in C++ überprüfen? (umständlich könnte ich alle Klassen, die von B erben, einzeln mit typeid abfragen, aber finde ich keine schöne Lösung)

    Vielen Dank schonmal!



  • susie schrieb:

    Kann man sowas auf einfache Art überhaupt in C++ überprüfen? (umständlich könnte ich alle Klassen, die von B erben, einzeln mit typeid abfragen, aber finde ich keine schöne Lösung)

    Es gibt kein Reflektion unter C++. Du musst tatsächlich wenn über typeid, dynamic_cast oder ähnliches gehen. Performanter wäre es aber wenn es garnicht nötig wäre (z.B. über sauber definierte Schnittstellen & Polymorphie).

    cu André



  • Wenn du dir so eine Frage stellst, dann hast du ein Designproblem.



  • Hi,

    also ich verstehe Dein Problem nicht ganz. Wenn

    susie schrieb:

    ...
    ...in diesem Array aber faktisch Pointer von Subklassen von A (z.B. C) drinstehen...

    dann brauchst Du doch keinen Test auf "A" für die Array-Alemente mehr.

    Mein Tipp beim Design: Denke nicht zu früh in technischen Kategorien ("Wer erbt von wem ?", "In welchen Container packe ich die Teile ?", ....), sondern schreibe mal im Klartext auf, was Du eigentlich tun möchtest - und lies Dir das mal nüchtern durch.

    Wenn dann so Sachen drin sind wie: "... und wenn das nächste Element in meiner Liste dann ein Auto ist, dann steige ein und fahre los, wenn es aber eine Katze ist, dann reinige ihr Katzenklo...", dann sollte Dir schon auffallen, dass das fachlich unausgegoren ist, weil Du versucht hast, 2 Dinge miteinander zu verquicken, die überhaupt nichts miteinander zu tun haben.

    So sollte man fachliche Abläufe nicht entwerfen ... und erst Recht nicht programmieren.

    Gruß,

    Simon2.



  • Hallo,

    Simon2 schrieb:

    also ich verstehe Dein Problem nicht ganz. Wenn

    susie schrieb:

    ...
    ...in diesem Array aber faktisch Pointer von Subklassen von A (z.B. C) drinstehen...

    dann brauchst Du doch keinen Test auf "A" für die Array-Alemente mehr.

    Nein, ich brauche ja einen Test auf B.
    Also, wenn ich folgenden Vererbungsbaum habe:

    A
    	 /	  \
        B		B1
     /    \	 /   \
    C	C1	D	D1
    

    In dem Array sind nur Elemente der untersten Klassen, aber C und C1 haben eben ähnliche Eigenschaften, weil sie beide von B abgeleitet sind. Dann interessiert mich, ob ein Element sich von B ableitet oder eben von B1.

    Ja, an sich stimmt es schon, das mit Polymorphie zu machen, wäre besser, aber hier geht es nur teilweise. Ich finde meinen Ansatz auch nicht schön, wenn jemand eine bessere Idee hat, nur her damit 🙂

    Das Problem: ich habe ein 3-dimensionales Array. 2 Dimensionen sind, um den Raum zu beschreiben, in dem die Arrayelemente sind, die 3. Dimension ist, um bis zu 3 Arrayelemente diesem Ort zuzuordnen. Und diese Arrayelemente sind alle Pointer auf Objekte von Klassen derselben Vererbungshierarchie (analog oben).
    So, bei vielem Verhalten der Objekte ist nur ausschlaggebend, zu welcher Klasse das Objekt gehört, das habe ich auch mit virtuellen Funktionen gelöst. Aber manches Verhalten der Objekte ist davon abhängig, welche beiden anderen Objekte in den anderen Arrayelementen drinstehen (also am selben Ort vorhanden sind). Ich wüßte nicht, wie ich das da mit Polymorphie hinkriegen könnte...



  • susie schrieb:

    ...Nein, ich brauche ja einen Test auf B...

    OK, sorry, das hatte ich mißverstanden.

    susie schrieb:

    ...Ich finde meinen Ansatz auch nicht schön, wenn jemand eine bessere Idee hat, nur her damit...

    Noch eine andere Idee wäre, die Verwaltung in eine eigene Klass auszulagern, die intern zwei Containern hält (vector<B*> und vector<B1*>). Die weiß dann, aus welchem Container sie sich jeweils bedient....
    Aber mir ist bislang noch kein Fall untergekommen, in dem ein "Mischcontainer" (egal, ob direkt mit STL-Containern oder gekapselt) wirklich die beste Wahl gewesen wäre.

    Gruß,

    Simon2.



  • Um es etwas besser zu verstehen, versuche ich das Problem mal anders darzustellen (Pseudocode ohne Typangabe, da mit unbekannt):

    // Wenn ich richtig verstehe entspricht jeder Eintrag deines Arrays
    // etwas mit der folgenden Struktur:
    struct
    {
      ort_x
      ort_y
      objekt
    }
    
    // Oder "quasi":
    
    std::set<Koordinate, Ortsobjekt>
    

    Es gibt einen Spruch der besagt, das wenn man etwas nicht direkt Lösen kann, eine weitere Indirektion helfen könnte. In deinem Fall vielleicht ein Objekt das pro Koordinate einmal existiert und die Unterelemente aufnimmt. Sprich irgendwas wie:

    std::map<Koordinate, Ortsobjekte>

    Und die Klasse die ich hier mal Ortsobjekte genannt habe, könnte vielleicht die von dir gewünschte Logik halten. Die Frage ist nur ob du während des Setzens der Einzelelemente in diese Liste noch weißt was sie sind.

    Ich kann gänzlich falsch liegen, da ich aus deinen Informationen nicht also viel herauslesen kann.

    cu André



  • Hallo,

    danke für Eure Antworten!

    @Simon2:
    Du meinst, ein 2-dimensionales Array, und die Elemente dieses Arrays sind die Verwaltungsklasse, die 2 Container enthält, einen für B, einen für B1 und all das, was ich vorher in der 3. Arraydimension auf die Indizes 0,1,2 verteilt habe, steht stattdessen in diesen Containern? Also Objekte von C und C1 stehen im Container für B, Objekte von D und D1 stehen in B1?
    Hm, das wär auch ne Idee, muß ich nochmal drüber nachdenken, wie das mit anderen Sachen zusammenpaßt.
    Was meinst Du mit "Mischcontainer"? Den Vorschlag von Dir (vector<B*> und vector<B1*>)?

    @asc:
    Wenn ich Deinen Vorschlag richtig verstanden habe, wäre das ein Set oder Map, bei dem pro Koordinate ein Objekt existiert, welches die Unterelemente enthält.
    Aber 1. sollte es schon ein 2-dimensionales Array als Grundstruktur sein (weil ich von einem anderen Programm auch ein räumlich deckungsgleiches Array verwende), 2. ist bei dem von Dir vorgeschlagenen Ortsobjekt ja weiterhin das Problem, daß das eine Unterobjekt wissen muß, ob das andere Unterobjekt zu B oder zu B1 gehört, damit es sich richtig verhält. Oder hab ich da irgendwas überlesen?

    Hm, sowohl wenn ich die Überprüfung mit typeid mache als auch wenn ich es so mache wie Simon2 vorgeschlagen hat, ist das Problem/Unschöne daran ja, daß die Vererbungshierarchie jenseits dieser Hierarchie quasi "festverdrahtet" im Code steht und man bei Änderungen in der Vererbungshierarchie an mehreren Stellen ändern muß. Oder gibts noch andere Gründe, wieso man sowas nicht so schön findet?

    Vielleicht wäre eine Idee, in der Klasse A eine virtuelle Funktion "isB()" zu schreiben, die testet, ob ein Objekt zu B gehört. Und die Implementierung dieser Methode in den Klassen B, C und C1 liefert dann eben "true", die anderen Klassen "false".
    Ist dann viel "unnützer" Code drin, aber soviel zu ändern hätte ich bei einer Veränderung der Klassenhierarchie dann nicht mehr.
    So ähnlich mach ich es ja auch um die jeweils richtigen Klassenvariablen zu verwenden.
    Scheint mir soweit der beste Kompromiß zwischen Aufwand und "Eleganz" zu sein (habs ja soweit alles schon mit einem 3-dimensionalen Array programmiert. Werde aber über das mit den beiden Containern nochmal nachdenken)

    Danke nochmal!



  • susie schrieb:

    2. ist bei dem von Dir vorgeschlagenen Ortsobjekt ja weiterhin das Problem, daß das eine Unterobjekt wissen muß, ob das andere Unterobjekt zu B oder zu B1 gehört, damit es sich richtig verhält. Oder hab ich da irgendwas überlesen?

    Solange du Informationen nicht lieferst um bessere Vorschläge zu machen, kann ich nur Raten.

    Falls du im Moment des Füllens des Arrays das Objekt gewusst hättest, hätte ich sage mal die von mir hineingezwängte Proxyklasse die Informationen (z.B. durch einen unterschiedlichen Aufruf) mitgeliefert bekommen können, und intern entsprechend das Objektverhalten angepasst.

    Die Informationen reichen aber nicht aus. Das einzige was ich sehe ist, das du Sicherlich dein Design grundlegend überdenken solltest. Es gibt meist (immer?) einen sinnvolleren Weg als über typeid&co zu gehen.

    cu André



  • susie schrieb:

    ...
    @Simon2:
    Du meinst, ein 2-dimensionales Array, ...Also Objekte von C und C1 stehen im Container für B, Objekte von D und D1 stehen in B1?
    ...

    Im Prinzip schon, aber eben nicht als 2D-Array, sondern eben:

    susie schrieb:

    ...
    Was meinst Du mit "Mischcontainer"? Den Vorschlag von Dir (vector<B*> und vector<B1*>)?

    Mal als Skizze:

    class mischContainer {
       vector<B*> alleB;
       vector<B1*> alleB1;
       bool actPosInAlleB;
       size_t actPos;
    public:
       void insert(B* b) { alleB.push_back(b); }
       void insert(B1* b1) { alleB1.push_back(b1); }
       void doIt() { 
          if(actPosInAlleB) alleB[actPos]->einsteigenUndFahren();
          else alleB1[actPos]->katzenKloReinigen();
       }
       bool next(); // Hier musst Du bestimmen, wie durch die vectoren gesteppt werden soll
       ~mischContainer(); // es Muss noch geklärt werden, wer den vector-Inhalt löscht !!
    };
    
    int main() {
       mischContainer cont;
       cont.insert(new B());
       cont.insert(new B1());
       // ....
    
       while(cont.next()) cont.doIt();
    

    Nur als ganz grobe Skizze .... wäre natürlich als eigenes Container-/Iterator-Paar schöner ...
    und wie gesagt: Ich rate eher zu einer kompletten Trennung oder Symbiose (mit identischer Schnittstelle).

    Alternativ noch eine Proxy-Klasse:

    class B_und_B1_Proxy {
       B* b;
       B1* b1;
    public:
       B_und_B1_Proxy(B* in) : b(in), b1(0) {}
       B_und_B1_Proxy(B1* in) : b(0), b1(in) {}
       void doIt() { 
          if(b) b->einsteigenUndFahren();
          if(b1) b1->katzenKloReinigen();
       }
    };
    
    int main() {
       vector<B_und_B1_Proxy> cont;
       cont.push_back(new B());
       cont.push_back(new B1());
       // ....
    
       for(vector<B_und_B1_Proxy>::iterator it = cont.begin(); it != cont.end(); ++it) {
          it->doIt();
       }
    

    Gruß,

    Simon2.



  • @asc:

    doch, beim Füllen des Arrays weiß ich schon, welches Objekt drinsteht (die Objekte, die drin stehen, können sich im Laufe der Zeit wieder ändern, aber auch dann weiß ich beim Füllen/Ändern, zu welcher Klasse sie gehören).

    Welche Informationen brauchst Du noch?

    hätte ich sage mal die von mir hineingezwängte Proxyklasse die Informationen (z.B. durch einen unterschiedlichen Aufruf) mitgeliefert bekommen können, und intern entsprechend das Objektverhalten angepasst.

    Was ist eine Proxyklasse? Meinst Du das, was Du oben als Klasse "Ortsobjekte" bezeichnet hast?
    Falls ja, wie sollte das funktionieren mit dem "intern entsprechend das Objektverhalten angepasst"?

    Es gibt meist (immer?) einen sinnvolleren Weg als über typeid&co zu gehen.

    Was hältst Du denn von meiner Idee oben mit dem "isB()"?

    @Simon2:

    Muß da mal in Ruhe drüber nachdenken, so schnell durchdringe ich das nicht. Aber meinem ersten Eindruck nach hat Dein Ansatz andere Vorteile.
    Blöd nur, wenn man gewisse Vorstellungen von einem eigenen Ansatz hat und dann alles nochmal grundlegend überdenken muß, da bin ich wohl im Denken nicht flexibel genug (v.a. wo ich schon angefangen habe, es als Array zu implementieren).

    Aber reinpacken in die Container täte ich dann Objekte von C und C1 bzw. D und D1? Nur um es richtig zu verstehen...

    Ich rate eher zu einer kompletten Trennung oder Symbiose (mit identischer Schnittstelle).

    Trennung oder Symbiose von was?
    Ich hatte zwar vorher schon objektorientiert programmiert, aber in C++ stecke ich da noch in den Anfängen, da verstehe ich nicht gleich soviel.

    Danke!



  • susie schrieb:

    Was ist eine Proxyklasse? Meinst Du das, was Du oben als Klasse "Ortsobjekte" bezeichnet hast?
    Falls ja, wie sollte das funktionieren mit dem "intern entsprechend das Objektverhalten angepasst"?

    Wenn dir Proxyklasse nichts sagt, solltest du dringend mal googlen. Aber kurz beschrieben:

    Eine Proxyklasse steht als stellvertreter für eine oder mehrere andere Klassen, und vermittelt zu denen. Im einfachsten Fall ist eine Proxyklasse von der Schnittstelle 1:1 mit dem dahinter liegenden Objekt identisch, nur in der Implementierung macht der Proxy noch etwas. Ein sehr gutes Beispiel kann z.B. sein das du ein sehr großes Objekt hast, das nur bei Bedarf geladen werden soll. Ein Proxy könnte nun als Stellvertreter dienen, und bei einem Zugriff auf die Schnittstelle das Objekt nachladen und dann die Aufrufe weiterdeligieren.

    In deinen Fall würde es etwas mehr tun müssen. Hierzu fehlen mir aber tatsächlich noch Informationen, so das meine Erklärung auch nur auf Annahmen bassiert (Und es gibt vielleicht auch ein deutlich besseres Design).

    Die Steuerung des Verhaltens wollte ich nun in den Proxy verschieben. Wenn du eh während der Zuordnung noch weißt welches Objekt was ist, könnte das ggf. durch abweichende Setter geregelt werden. Je nach dem welches Setter aufgerufen wird, schaltest du intern auf einen anderen Modus um (Wechselst die Strategie) - oder passt die Elemente entsprechend an (Darfst aber nicht vergessen weitere Elemente die hinzukommen auch anzupassen).

    susie schrieb:

    Es gibt meist (immer?) einen sinnvolleren Weg als über typeid&co zu gehen.

    Was hältst Du denn von meiner Idee oben mit dem "isB()"?

    Kurz gesagt: garnichts
    Grund: isB() liefert eigentlich keine übergreifenden Nutzinformationen. Das Währe so als Würdest du einer Klassenhierachie von Fahrzeugen eine Methode "isFordKa()" geben. Viel zu speziell.

    cu André



  • Bei Polymorphie ist ja das tolle, dass Pointer automatisch wissen, welche Funktionen sie aufrufen müssen. Natürlich klappt das nicht immer auf Anhieb, etwas so Textbookmäßig zu designen, aber darauf abzielen sollte man schon (find ich).

    Was du machen willst klingt im weiteren Sinne mathematisch/wissenschaftlich, da kommt man meistens mit nem quick & dirty hack zurecht, weil die Programme in der Regel nicht sehr erweiterbar sein brauchen. Was _genau_ willst du denn machen?

    Wenn ichs recht verstehe hast du ein 2D Gitter, wo auf jeder position eine (sagen wir mal) Liste von bis zu drei Objekten stehen, die alle indirekt von A erben, bei denen aber für bestimmte Operationen wichtig ist, welche Klasse sie genau haben, ja?

    Sagen wir mal das wäre so was wie ein Kochrezept, wo du zum Schluss so was machen willst wie:

    für alle Schüsseln i = 1 ... n und j = 1 ... m{
       if(schüssel[i][j].checkContents() == Ei, Milch, Mehl){
            schüssel[i][j].backeBackeKuchen();
       }
    }
    

    Falls du die gemeinsame Basisklasse 'Backzutat' wirklich wirklich brauchst, und du nicht ungeschickterweise Vererbung anstelle von Komposition verwendet hast, würd ich spontan so was machen wie:

    Singleton basierte Lösung (im wesentlichen werden Objekte mit ihrer Klasse identifiziert, es gibt also nur eine Instanz):

    Jede abgeleitete Klasse meldet sich mit einer static instanz bei einer registry in der obersten Basisklasse an (map<string, Backzutat*), so dass diese alle subklassen kennt, auch wenn neue dazukommen von denen du jetzt noch nix weißt.

    Die bis zu drei Einträge wären dann pointer auf die jeweils eine Instanz der Klasse zu denen sie gehören.

    Dann schreibst du noch ne virtuelle funktion namens 'string getType()', mit der jede abgeleitete Klasse den String zurückgibt, unter der sie in der registry zu finden ist, und wenn du jetzt auf alle verfügbaren Kochrezepte prüfen willst, zählst du in jedem Topf alle Instanzen von allen in der registry angemeldeten Typen (in eine map<string, unsigned>). Dann schreibst du dir des weiteren ne library mit allen verfügbaren Kochrezepten (in ähnlicher Weise, Kochrezept-registry map<string, kochrezept*>) und checkst dann diese library durch, für welche Rezepte die Zutaten erfüllt sind. Rückgabewert wäre dann zB eine Liste wo alle machbaren Rezepte drinstehen.

    Das Problem ist natürlich, dass die Objekte nicht mehr wirklich unterscheidbar sind, wenn dein Programm also von der Identität der bis zu drei Objekte abhängt, und nicht nur von ihrem Typ, gehts glaub ich nicht so ohne weiteres.

    Das Grundprinzip ist hier das Singleton pattern (google/wikipedia) mit Vererbung frei nach Gamma, Helm, Johnson, Vlissides aka Gang of Four. Das geht bestimmt eleganter, aber ohne genau zu wissen, was du brauchst bzw. machen willst, fällt mir nix besseres ein.



  • Hallo,

    danke nochmal für die Beiträge.

    Ja, es ist ein wissenschaftliches Projekt, wo die Programmierung eben nur Mittel zum Zweck ist. Gegenwärtig muß ich mich mit der Implementierung beeilen (wie das in wissenschaftlichen Projekten oft so ist), daher werde ich jetzt doch erstmal meinen Ansatz (mit dem "isB()" testen) verwenden, weil mir das intuitiv am klarsten ist und ich (gegenwärtig) nicht so direkt Nachteile sehe. HAb auch grad den Kopf vom inhaltlichen her voll, da möchte ich mich augenblicklich nicht so in die Implementierungsgeschichten reinknien, sonst gibts Knoten im Kopf 😉
    Ich werd in Ruhe nochmal über einige Sachen nachdenken, wenn das inhaltliche ein bißchen festgezurrt ist, einige Sachen an meinem Implementierungsansatz gefallen mir ja auch nicht.
    Denn momentan soll es zwar nicht erweitert werden, aber das kann sich auch wieder ändern.
    Vielleicht ist der eine oder andere dann ja noch da, wenn ich mir Eure Beiträge dann nochmal genauer anschaue und die nicht verstehe, dann kann ich auch noch genauere Infos geben.

    Danke nochmal und viele Grüße

    Ach ja, und nicht vergessen: :schland: 😉


Anmelden zum Antworten