C++ typeof Äquivalent / switch nach Objekttyp



  • "typeid()" ist der moderne Ersatz: https://msdn.microsoft.com/de-de/library/ms235260.aspx

    Mit "switch()" funktioniert "typeid()" anscheinend nicht. Aber so geht es:

    #include <iostream>
    using namespace std;
    
    class Tier
    {
    public:
    	virtual void Fressen() {};
    };
    
    class Elster : public Tier
    {
    public:
    	void Fressen()
    	{
    		cout << "Elster frisst\n";
    	}
    };
    
    class Spinne : public Tier
    {
    	void Fressen()
    	{
    		cout << "Spinne frisst\n";
    	}
    };
    
    class Raupe : public Tier
    {
    	void Fressen()
    	{
    		cout << "Raupe frisst\n";
    	}
    };
    
    void main()
    {
    	Tier* t = new Spinne();
    
    	if (typeid(*t) == typeid(Spinne))
    		t->Fressen(); => "Spinne frisst"
    	if (typeid(*t) == typeid(Elster))
    		t->Fressen(); => "Elster frisst"
    	if (typeid(*t) == typeid(Raupe))
    		t->Fressen(); => "Raupe frisst"
    
    	cin.get();
    }
    

    Das erpart beim "switchen" den "Typ-Flag", der dann auch nicht mehr zugewiesen werden muss. Finde ich gut!

    "typeid()" in Verbindung mit "switch()" wäre aber sicher noch performanter, weil nicht die ganzen if() Abfragen durchlaufen werden müssen. Hat jemand eine Idee, wie das mit "switch()" funktionieren könnte?



  • Ob das jetzt besser ist als eine virtuelle Funktion aufrufen 😕



  • Herr Intel hat doch ein Jahr nach dem Artikel den Prozessor gepimpt, der hat jetzt ca 50000 zusätzliche Transistoren verbaut, um virtuelle Funktionsaufrufe flott hinzukriegen.



  • mireiner schrieb:

    Das erpart beim "switchen" den "Typ-Flag", der dann auch nicht mehr zugewiesen werden muss. Finde ich gut!

    Und wo war da jetzt der Funktionsaufruf? Ich dachte du wolltest den virtual function call optimieren?



  • roflo schrieb:

    Ob das jetzt besser ist als eine virtuelle Funktion aufrufen 😕

    "Besser" ist sicher eine Frage des Einzelfalls - bei meinem Programm zumindest 4% schneller (worauf ich aber verzichten kann), ABER die Rotationen laufen via "switch" nahezu ruckelfrei! Es ist das minimale "Stottern" der Grafikrotationen, das mich noch stört und die ich nicht wegbekommme. Eine gleichmäßig fliessende Grafikanimation ist schon was Feines, allein schon wegen der Ruhe die sie ausstrahlt.


  • Mod

    mireiner schrieb:

    "typeid()" in Verbindung mit "wwitch" wäre aber sicher noch performanter, weil nicht die ganzen if Abfragen durchlaufen werden müssen. Hat jemand eine Idee, wie das mit "switch" funktionieren könnte?

    Wie wäre es, wenn du der Basisklasse einen Methodenzeiger spendierst, der dann bei der Konstruktion einer abgeleiteten Klasse passend gesetzt wird? Dann bräuchten überhaupt keine Abfragen irgendeiner Art gemacht werden für den kleinen Preis von ein paar Byte Speicher.



  • SeppJ schrieb:

    Wie wäre es, wenn du der Basisklasse einen Methodenzeiger spendierst, der dann bei der Konstruktion einer abgeleiteten Klasse passend gesetzt wird? Dann bräuchten überhaupt keine Abfragen irgendeiner Art gemacht werden für den kleinen Preis von ein paar Byte Speicher.

    = virtuelle Methode



  • mireiner schrieb:

    roflo schrieb:

    Ob das jetzt besser ist als eine virtuelle Funktion aufrufen 😕

    "Besser" ist sicher eine Frage des Einzelfalls - bei meinem Programm zumindest 4% schneller (worauf ich aber verzichten kann), ABER die Rotationen laufen via "switch" nahezu ruckelfrei! Es ist das minimale "Stottern" der Grafikrotationen, das mich noch stört und die ich nicht wegbekommme. Eine gleichmäßig fliessende Grafikanimation ist schon was Feines, allein schon wegen der Ruhe die sie ausstrahlt.

    Das Ruckeln liegt nicht daran.



  • roflo schrieb:

    SeppJ schrieb:

    Wie wäre es, wenn du der Basisklasse einen Methodenzeiger spendierst, der dann bei der Konstruktion einer abgeleiteten Klasse passend gesetzt wird? Dann bräuchten überhaupt keine Abfragen irgendeiner Art gemacht werden für den kleinen Preis von ein paar Byte Speicher.

    = virtuelle Methode

    Fast. Ist eine Indirektion weniger.



  • Stimmt.



  • SeppJ schrieb:

    Wie wäre es, wenn du der Basisklasse einen Methodenzeiger spendierst, der dann bei der Konstruktion einer abgeleiteten Klasse passend gesetzt wird? Dann bräuchten überhaupt keine Abfragen irgendeiner Art gemacht werden für den kleinen Preis von ein paar Byte Speicher.

    Das hört sich fantastisch an! Aber ehrlich gesagt, komme ich da an meine Grenzen. Habe keine Ahnung, wie das programmiert werden kann. Könntest Du vielleicht ein lauffähiges Minimal-Beispiel zeigen?



  • sebi707 schrieb:

    roflo schrieb:

    SeppJ schrieb:

    Wie wäre es, wenn du der Basisklasse einen Methodenzeiger spendierst, der dann bei der Konstruktion einer abgeleiteten Klasse passend gesetzt wird? Dann bräuchten überhaupt keine Abfragen irgendeiner Art gemacht werden für den kleinen Preis von ein paar Byte Speicher.

    = virtuelle Methode

    Fast. Ist eine Indirektion weniger.

    Welche aber nichts kosten würde unter der Annahme, daß die Speicherseiten mit den VTBLs heiß bleiben und es einen Call-Befehl gäbe, der den Tabellen-Nachkucker gleich in einem Aufwasch mitmachen könnte.


  • Mod

    mireiner schrieb:

    SeppJ schrieb:

    Wie wäre es, wenn du der Basisklasse einen Methodenzeiger spendierst, der dann bei der Konstruktion einer abgeleiteten Klasse passend gesetzt wird? Dann bräuchten überhaupt keine Abfragen irgendeiner Art gemacht werden für den kleinen Preis von ein paar Byte Speicher.

    Das hört sich fantastisch an! Aber ehrlich gesagt, komme ich da an meine Grenzen. Habe keine Ahnung, wie das programmiert werden kann. Könntest Du vielleicht ein lauffähiges Minimal-Beispiel zeigen?

    Ja, das Minimalbeispiel ist ganz einfach: virtual. Das ist ja gerade der ganze Witz. Du brichst dir hier einen ab für seit 20 Jahren veraltete Optimierungen.

    Hör auf volkard!

    volkard schrieb:

    Das Ruckeln liegt nicht daran.

    Da hat volkard sicher recht. Ich bezweifle auch sehr deine Messmethode, wenn du behauptest, 4% gegenüber der Implementierung von virtuellen Methoden deines Compilers gewonnen zu haben. Das schaffst du ganz sicher nicht mit typeid/typeof & Co. Denn was glaubst du wohl, wie typeid und Konsorten implementiert sind?

    Zeig lieber dein eigentliches Problem, also das Ruckeln, und frag, wie man das flüssig hin bekommt. Wobei das Problem eventuell im Spiele- und Grafikforum besser aufgehoben sein könnte.



  • @mireiner
    Damit das ganze ne Chance hat 'was zu bringen darf der Aufruf nicht indirekt erfolgen - die Methode muss "direkt" aufgerufen werden. Damit sie inline erweitert werden kann. Denn nur dort sind die wirklich mächtigen Optimierungen zu Hause.

    Das kannst du erreichen durch
    * Einen mächtig schlauen Compiler der mächtig schlau Devirtualization betreiben kann, und ausreichend Glück dass er es an der Stelle wo du es brauchst auch macht.
    * Einen mächtig schlauen Compiler den du mit Profile-Guided-Optimization unterstützt + immer noch ein gutes Stück Glück.
    * Die switch-case Variante.

    Ein Methodenzeiger bringt nichts, da hier der Aufruf erst wieder indirekt erfolgt, also kein Inlining möglich ist (zumindest praktisch nicht, auch wenn auch hier ein Compiler theoretisch optimieren könnte).

    Auf den mächtig schlauen Compiler würde ich mich nicht verlassen wollen.

    Bleibt also noch die switch-case Variante.

    Dabei ist natürlich wichtig dass du an das auf was du switch -st möglichst schnell dran kommst. Lösung: pack nen int in die Basisklasse.

    switch (m_myType) 
    { 
    case Base::MyType: 
         Base::foo(); break; 
    case Sub1::MyType:
         static_cast<Sub1*>(this)->Sub1::foo(); break; 
    case Sub2::MyType:
         static_cast<Sub2*>(this)->Sub2::foo(); break; 
    default:
         this->foo(); break; 
    }
    

    Bloss ... dummerweise wird volkard vermutlich Recht haben. Das Ruckeln wird wohl nicht dort verursacht werden. Und dann kannst du es dort natürlich auch nicht beheben.



  • sebi707 schrieb:

    wenn ich das richtig sehe dann ist das ein Vorschlag für eine Optimierung in den Compilern, nichts zum selbst programmieren bei jedem Funktionsaufruf. Dazu ist der Artikel von 1996. Falls diese Optimierung sinnvoll ist/war haben heutige Compiler das hoffentlich eingebaut.

    Ja. Ist drin, sollte alle "grossen" aktuellen Compiler können (MSVC, GCC, Clang, Intel)
    Nennt sich Devirtualization.
    Ist aber natürlich, wie viele Optimierungen, nix worauf man sich 100% verlassen kann. Wenns um die Wurst geht darf man sowas in 1-2 Funktionen ruhig mal selbst machen. Geht bloss meist nicht um die Wurst 🙂


  • Mod

    Das erinnert mich an das oft vergessene C++11-Feature final, mit dem man dem optimierenden Compiler die Annahmen garantieren kann (die dann hoffentlich auch stimmen!), die er braucht, um diese Optimierung unbekümmert durchführen zu können.



  • Bringt final da wirklich was?

    Der üblicher Fall ist doch dass die Stelle wo der kritische Aufruf steht eben gerade nicht in der final Klasse ist (bzw. nicht in der Klasse wo die aufgerufene Funktion final wird).

    Und dann bringt es dem Compiler schätze ich nicht viel zu wissen dass bestimmte abgeleitete Klassen final wären.

    Er muss also trotzdem ein if (this->vtbl->fun[123] == &Derived1::Fun) o.Ä. reinknallen. Und das reicht auch ohne final .

    Die schwierigen Fragen für den Compiler sind denke ich eher: wo zahl sich das überhaupt aus und welche abgeleiteten Klassen sollten als Kandidaten geprüft werden?


  • Mod

    Wenn du den Fall, in dem final in diesem Zusammenhang etwas bringen würde, als unrealistisch weg definierst (womit du aber durchaus Recht haben magst), dann bringt es natürlich nichts 🙂



  • Also, final bringt nur dann was, wenn der Compiler den most derived type nicht kennt, aber trotzdem noch Zusatzinformationen hat.

    Also z.B. hier:

    Base* base=createObject();
    static_cast<Derived*>(base)->foo();
    

    Ohne final in Derived::foo() resultiert das trotzdem in einen virtuellen Funktionsaufruf, mit final kann Derived::foo() direkt aufgerufen werden.

    Wenn man static_cast<Derived*>(base)->Derived::foo() schreibt, ist's eh egal.



  • ;qwerty schrieb:

    Wenn man static_cast<Derived*>(base)->Derived::foo() schreibt, ist's eh egal.

    Ich denke typisch ist eher der Fall base->foo() .
    Und da bringt final wohl auch wenig.


Anmelden zum Antworten