C++ typeof Äquivalent / switch nach Objekttyp



  • In dem PDF-Dokument: "AN ENABLING OPTIMIZATION FOR C++ VIRTUAL FUNCTIONS" (über google.de suchen) wird "switch" mit "typeof" kombiniert:

    "Base" ist die Basisklasse, "Sub1" und "Sub2" davon abgeleitete Klassen:

    switch(typeof(this)) 
    {
    case Base:
        Base::foo(); break;
    case Sub1:
        Sub1::foo(); break;
    case Sub2:
        Sub2::foo(); break;
    default:
        this->foo(); break;
    }
    

    Der Artikel stammt aus den 90er Jahren. MSCV2013 kennt keinen "typeof" Befehl. Gibt es ein heutiges aktuelles Äquivalent dazu? Kann mit MSVC2013 oder MSVC2015 ein Code mit der gleichen Funktion geschrieben werden?


  • Mod

    Ja, man kann das gleiche in modernem C++ erreichen, ist aber hochgradig kompliziert:

    foo();
    

  • Mod

    Ich glaub', das lass lieber sein. Am Ende verwechselst du das gemeinte typeof noch mit decltype .



  • mireiner schrieb:

    In dem PDF-Dokument: "AN ENABLING OPTIMIZATION FOR C++ VIRTUAL FUNCTIONS" (über google.de suchen) wird "switch" mit "typeof" kombiniert

    Ich habe den Artikel nur überflogen aber 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.



  • "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.


Anmelden zum Antworten