alternative für RTTI



  • hallo 🙂

    ich bin grade dabei mir eine gui zu schreiben. alle elemente haben eine
    gemeinsame basisklasse "element". ich speichere in jedem übergeordnetem
    element wie z.b. einem fenster ein vector mit zeigern auf die untergeordneten
    elemente wie z.b. ein knopf.

    über virtuelle funktionen wird jeweils die richtige funktion ausgewählt, das ist
    auch kein problem. das tritt erst auf wenn man über die schnittstelle
    elementspezifische daten eines elementes ändern will. den nicht jedes element
    hat die selben daten.

    jedes element bekommt bei der erzeugung eine einheitliche id zugewiesen,
    und über die bekommt man dann auch den zeiger auf das richtige element.

    da die id statisch ist, gibt es keine probleme. allerdings muss man dazu den
    zeiger casten. das ist natürlich schlechtes design 😞

    beispielcode

    class element
    {
    public:
        element *getelement(unsinged id);
    private:
        std::vector<element *> childelements;
    };
    class window : public element
    {
    };
    class button : public element
    {
    };
    
    // ...
    
    gui *g = new gui(...)
    window *w = g->addwindow(..., 1742);
    w->addbutton(..., 8765);
    
    //...
    
    reinterpret_cast<button *>(g->getelement(1742)->getelement(8765))->settext(...);
    

    dynamic_cast oder rtti möchte ich dabei gerne vermeiden.
    auch das implementieren sämtlicher methoden in der basisklasse
    ein container pro klasse würde zwar gehen aber würde irgentwann unübersichtlich.

    wie könnte man das eleganter lösen?

    MfG fragezeichen?



  • fragezeichen? schrieb:

    dynamic_cast oder rtti möchte ich dabei gerne vermeiden.

    Warum?



  • fragezeichen? schrieb:

    reinterpret_cast<button *>(g->getelement(1742)->getelement(8765))->settext(...);
    

    magste

    g->getelementwindow(1742)->getelementbutton(8765))->settext(...);
    

    ?



  • audacia schrieb:

    fragezeichen? schrieb:

    dynamic_cast oder rtti möchte ich dabei gerne vermeiden.

    Warum?

    weil man trotzdem casten muss und dynamic_cast für (geringe) performance-
    probleme bekannt ist

    volkard schrieb:

    magste

    g->getelementwindow(1742)->getelementbutton(8765))->settext(...);
    

    ?

    das wäre schon ganz schön, nur ist die methode settext nicht bei allen
    abgeleiteten klassen vorhanden. natürlich könnte man den ganzen
    satz funktionen in der basisklasse implementieren, aber das würde dann
    schon ein bisschen viel. winapi machts ja auch so.

    im moment sieht es so aus als ob jede klasse einen eigenen container bekommt,
    womit ich aber das getelement in der basisklasse vergessen kann...

    was auch noch möglich wäre, sind zwischenklassen, die alle childelemente
    einer klasse zusammenfassen. dann stellt sich allerdings das problem wie
    eine progressbar und ein editfeld in eine klasse passen sollen.



  • fragezeichen? schrieb:

    dynamic_cast für (geringe) performance-probleme bekannt ist

    Gegenüber dem, was dem cast dann folgt, ist das aber ein Witz 😉

    Ich finde sowas in der Art ganz angenehm:

    class ChildWindow
    {
        public:
            // Lauter Sachen, die für alle sinnvoll sind:
            void show();
            void hide();
            // ...
    };
    
    class Checkbox : public ChildWindow
    {
        public:
            // Checkbox-spezifische Sachen:
            bool isChecked();
    };
    
    if ( main_wnd.get<Checkbox>(12345).isChecked() )
        // ...
    


  • fragezeichen? schrieb:

    volkard schrieb:

    magste

    g->getelementwindow(1742)->getelementbutton(8765))->settext(...);
    

    ?

    das wäre schon ganz schön, nur ist die methode settext nicht bei allen
    abgeleiteten klassen vorhanden.

    aber bei allen buttons. und getelementbutton gibt ja nen Button* zurück.

    im moment sieht es so aus als ob jede klasse einen eigenen container bekommt, womit ich aber das getelement in der basisklasse vergessen kann...

    ich dachte nie an eigene container pro klasse.

    idee 💡
    g->getelement<Window>(1742)->getelement<Button>(8765))->settext(...);
    ok, ich geb's zu: das ist nur ne andere schreibweise für
    g->dynamic_cast<Window&>(getelement(1742)).dynamic_cast<Button&>(getelement(1765)).settext(...);



  • Badestrand schrieb:

    fragezeichen? schrieb:

    dynamic_cast für (geringe) performance-probleme bekannt ist

    Gegenüber dem, was dem cast dann folgt, ist das aber ein Witz 😉

    Also auf 64-Bit Windowssystemen können dynamic_casts ein Problem sein, vorallem wenn man ein paar 100000 pro Sekunde macht.
    http://blogs.msdn.com/junfeng/archive/2006/10/17/dynamic-cast-is-slow-in-x64.aspx
    http://archives.windowshpc.net/blogs/dev_gen/archive/2006/10/17/647.aspx
    Das wird doch unseren premature optimization meister freuen. :p

    fragezeichen? schrieb:

    jedes element bekommt bei der erzeugung eine einheitliche id zugewiesen,
    und über die bekommt man dann auch den zeiger auf das richtige element.

    Wozu dieses ID Zeugs? Warum erzeugst du nicht einfach einen Button und übergibst den dann, dann hast du schon den Pointer auf den Button und musst ihn nicht wieder holen?



  • volkard schrieb:

    fragezeichen? schrieb:

    volkard schrieb:

    magste

    g->getelementwindow(1742)->getelementbutton(8765))->settext(...);
    

    ?

    das wäre schon ganz schön, nur ist die methode settext nicht bei allen
    abgeleiteten klassen vorhanden.

    aber bei allen buttons. und getelementbutton gibt ja nen Button* zurück.

    sry hab das button überlesen 🙄

    sämmtliche template-methoden verstecken den dynamic_cast nur.
    bei einer 60fps anwendung mit einem vollen menü was auf mehreren seiten
    knöpfchen und schalter hat wird das schon ein bisschen viel.

    außerdem mag ich den cast nicht so. das die möglichkeit besteht dass der
    zurückgegebene zeiger nicht mit dem übergebenen identisch ist *schauder*
    und dann auch noch ein lookup im fileheader *schauderschüttel*

    meine jetzige lösung ist ein

    virtual edit	  *getedit() 	 {return 0;}
    virtual button	*getbutton()	{return 0;}
    virtual window	*getwindow()	{return 0;}
    // ...
    

    die jeweilige methode wird von den abgeleiteten klassen durch return this
    ersetzt. das klappt ganz gut, ist dem prinzip ziemlich ähnlich aber ist schneller.

    was haltet ihr davon?



  • fragezeichen? schrieb:

    meine jetzige lösung ist ein

    virtual edit	  *getedit() 	 {return 0;}
    virtual button	*getbutton()	{return 0;}
    virtual window	*getwindow()	{return 0;}
    // ...
    

    die jeweilige methode wird von den abgeleiteten klassen durch return this
    ersetzt. das klappt ganz gut, ist dem prinzip ziemlich ähnlich aber ist schneller.
    was haltet ihr davon?

    ja, das war mein vorschlag. ist schon ganz ok. vielleicht überdenkenswert, statt 0 zurückzugeben, ne exception zu werfen. hängt davon ab, wie viele fehler du machen willst.

    sämmtliche template-methoden verstecken den dynamic_cast nur.

    wer sagt das? ich hab nicht verraten, wie die innendrin gehen. da gibts unzählige möglichkeiten. zum beispiel

    template<typename T>
    T* getElement(int id)
    {
       Base* basep=privateGetGenericElement(id);
       if(basep->virtualGetClassEnum()!=T::staticGetClassEnum())
          return throw 4711;//oder return 0
       return static_cast<T*>(basep);
    }
    


  • Ich frage mich nur, woher du dann die ID's hast.. Wenn du die irgendwo speicherst, kannst du es ja machen, wie es ein Vorposter gesagt hat und gleich die Zeiger darauf speichern..



  • drakon schrieb:

    Ich frage mich nur, woher du dann die ID's hast..

    ich hab sie von "reinterpret_cast<button *>(g->getelement(1742)->getelement(8765))->settext(...);" im ersten posting.

    im letzten posting scheint fragezeichen? sie nicht mehr zu brauchen. 😕



  • volkard schrieb:

    drakon schrieb:

    Ich frage mich nur, woher du dann die ID's hast..

    ich hab sie von "reinterpret_cast<button *>(g->getelement(1742)->getelement(8765))->settext(...);" im ersten posting.

    im letzten posting scheint fragezeichen? sie nicht mehr zu brauchen. 😕

    Ah, ne Sorry. Ich habe auf den Post vor dir bezogen geschrieben. 😉

    Ich meinte im Programm allgemein. Wenn er die ID's irgendwo speichert, ala "myButtonIDContainer", dann ist das ganze Spiel ja eh für die Katze und man hätte gleich die Zeiger speichern können.



  • die IDs muss man sich halt merken. zeiger kann man sich aber nicht merken.
    entweder ich speichere irgentwo unmengen an zeigern, die nur für eine methode
    aufgerufen werden, oder ich löse das über IDs.

    (ich will in der rendermothode an einen knopf kommen der aus der main stammt)

    die z.b. getedit methode braucht keine id, sie wird ja von einem generischen
    zeiger aufgerufen, dessen typ zu ermitteln ist.

    bsp:

    guimain->getactivepage()->getelement(1)->getelement(1)->getprogressbar()->setpercent(val);
    

    getprogressbar gibt standardmäßig 0 zurück. nur wenn es sich wirklich um eine
    progressbar handelt, kommt this zurück. das vermeidet gleichzeitig das casten.

    exeptions, hm.. naja.. das ist auch geschmackssache, aber es vergrößert den code
    nur. außerdem fliegt die exeption eh wenn die methode 0 liefert.

    volkard schrieb:

    wer sagt das? ich hab nicht verraten, wie die innendrin gehen.

    weil

    volkard schrieb:

    ok, ich geb's zu: das ist nur ne andere schreibweise für
    g->dynamic_cast<Window&>(getelement(1742)).dynamic_cast<Button&>(getelement(1765)).settext(...);



  • premature optimization schrieb:

    Badestrand schrieb:

    fragezeichen? schrieb:

    dynamic_cast für (geringe) performance-probleme bekannt ist

    Gegenüber dem, was dem cast dann folgt, ist das aber ein Witz 😉

    Also auf 64-Bit Windowssystemen können dynamic_casts ein Problem sein, vorallem wenn man ein paar 100000 pro Sekunde macht.
    http://blogs.msdn.com/junfeng/archive/2006/10/17/dynamic-cast-is-slow-in-x64.aspx
    http://archives.windowshpc.net/blogs/dev_gen/archive/2006/10/17/647.aspx

    Wenn ich das richtig verstehe, ist das nicht Win64-, sondern MSVC64-spezifisch.
    Falls dem so ist: hoffen wir mal, daß CodeGear es besser macht 😉



  • dynamic_cast war bei MSVC immer schon langsam. Irgendwie krass dass MS es geschafft hat die noch langsamer zu machen. Und schade. Es gibt einige wenige Stellen wo dynamic_cast wirklich hilfreich wäre, und manchmal sind da auch performance-kritische Dinge dabei. 👎 für MS



  • fragezeichen? schrieb:

    volkard schrieb:

    wer sagt das? ich hab nicht verraten, wie die innendrin gehen.

    weil

    volkard schrieb:

    ok, ich geb's zu: das ist nur ne andere schreibweise für
    g->dynamic_cast<Window&>(getelement(1742)).dynamic_cast<Button&>(getelement(1765)).settext(...);

    oh. ich hab nicht dazugeschrieben, daß ich innen schon sowas wie deine virtuellen funktionen haben wollte. ich mag nämlich dynamic_cast gar nicht.
    anfangs dachte ich, daß dich die aufrufsyntax nervt und die unsicherheit, vielleicht nen nullzeiger zu benutzen.
    "ok, ich geb's zu: das ist nur ne andere schreibweise für" war aus sicht des aufrufers gemeint.

    die template-funktion getElement merke ich mir, die wird praktisch sein, wenn ich mal groß bin und ein MMORPG schreibe. über's netz fliegen ja nur IDs von den mobs, vielleicht ist es dann gut, wenn ich alle objekte mit IDs verwalte. macht auch das speichern und laden viel einfacher.



  • fragezeichen? schrieb:

    die IDs muss man sich halt merken. zeiger kann man sich aber nicht merken.
    entweder ich speichere irgentwo unmengen an zeigern, die nur für eine methode
    aufgerufen werden, oder ich löse das über IDs.

    Und das mit den IDs ist Quatsch wie du doch schon selber merkst. Ob du dir eine ID oder nen Pointer als Member einer Klasse hältst ist doch egal. Schau dir mal andere GUI Frameworks an.
    http://doc.trolltech.com/4.0/examples.html



  • volkard schrieb:

    die template-funktion getElement merke ich mir, die wird praktisch sein, wenn ich mal groß bin und ein MMORPG schreibe. über's netz fliegen ja nur IDs von den mobs, vielleicht ist es dann gut, wenn ich alle objekte mit IDs verwalte. macht auch das speichern und laden viel einfacher.

    es lebe das id system 😃

    premature optimization schrieb:

    Und das mit den IDs ist Quatsch wie du doch schon selber merkst. Ob du dir eine ID oder nen Pointer als Member einer Klasse hältst ist doch egal.

    wenn man die ids eh als member speichert, macht das wenig sinn, da hast du recht.
    wenn man aber statische ids vergibt, und in der lage ist sich diese 2min zu
    merken, kann man in einer anderen methode oder funktion (die keinen zugriff
    auf die verwaltungsklasse hat) den zeiger weiterverwenden.

    und wie volkard schon sagte, wenn man übers netz auf objekte zugreifen will,
    werden zeiger sicherlich probleme machen. man kann die gültigkeit nicht
    prüfen und das auf jedem client die daten an der selben stelle liegen, ist
    sehr unwahrscheinlicht.



  • volkard schrieb:

    die template-funktion getElement merke ich mir, die wird praktisch sein, wenn ich mal groß bin und ein MMORPG schreibe. über's netz fliegen ja nur IDs von den mobs, vielleicht ist es dann gut, wenn ich alle objekte mit IDs verwalte. macht auch das speichern und laden viel einfacher.

    Ok, dass verstehe ich jetzt nicht. War das ironisch gemeint oder ist der Zugriff auf Objekte per ID wirklich so toll 😕

    Kann man sich denn die Objekte (Buttons, Scrollbars usw...) nicht in unterschiedlichen Listen merken und vieleicht mit sowas hier den Zeiger holen?

    // Verschiedene Gui Elemente
    class Gui {
    public:
      enum type{
        Undefined,
        Button,
        Scrollbar
      }
    }
    
    // ein Steuerelement
    class Button public Control {
    private:
      int type;
    public:
      Button() {
        type = Gui::Button;
      }
    }
    
    // ein Menü mit Buttons usw...
    class MainMenu public Gui {
    protected:
      std::map<int,Button*> buttons;
      std::map<int,Scrollbar*> bars;
    public:
      bool typeOf(id, Gui::type);
      Button* getButton(id);
      Scrollbar* getScrollbar();
    
      void addElement( Control* ctrl, int posX, int posY );
    }
    
    // irgendwo im Code
    int foo {
      MainMenu menu;
    
      menu.addElement( new Button(1234), x, y );
    
      if( menu.typeOf(1234,Gui::Button) ) {
        Button* button = menu.getButton(id);
        button.setText("Ok");
      }
    
    }
    

    Ok der Code ist mit Vorsicht zu genießen (hab schon lange kein C++ mehr programmiert und jetzt auch keine Zeit die genaue Syntax rauszusuchen). Aber eigentlich könnte man doch auf diese Weise auch ohne dynamic Cast auskommen. Oder muss ich in addElement von Control* nach Button* casten?



  • fragezeichen? schrieb:

    meine jetzige lösung ist ein

    virtual edit	  *getedit() 	 {return 0;}
    virtual button	*getbutton()	{return 0;}
    virtual window	*getwindow()	{return 0;}
    // ...
    

    die jeweilige methode wird von den abgeleiteten klassen durch return this
    ersetzt. das klappt ganz gut, ist dem prinzip ziemlich ähnlich aber ist schneller.

    was haltet ihr davon?

    Es würde reichen, wenn du nur eine getFunktion in der Basisklasse definieren würdest.

    Basis:
    virtual element* getTyp() = 0;
    
    Button:
    virtual button* getTyp() { return this; }
    
    Window:
    virtual window* getTyp() { return this; }
    

    Nennt sich CoVariant-Returntype ...


Log in to reply