typeid() ... schlechter Stil?



  • Hallo,

    Ich programmiere gerade ein Spiel und schreibe zurzeit eine kleine Konsole für eben jenes. Diese Konsole enthält viele Variablen, im Folgenden "CVar"'s genannt..

    Mein Code bis jetzt:

    #include "cvar.h"
    
    int main()
    {
    	CVarSys sys;
    
    	sys.AddCvar<float>( "float_asd", 1.0f );
    	sys.AddCvar<bool>( "bool_test", true );
    	sys.AddCvar<int>( "int_someint", 12 );
    
    	std::cout << sys.GetCvarValue<float>( "float_asd" ) << std::endl;
    	std::cout << sys.GetCvarValue<bool>( "bool_menc9regay" ) << std::endl;
    	std::cout << sys.GetCvarValue<int>( "int_someint" ) << endl;
    
    	std::cin.get();
    	return 1;
    }
    
    #include <boost/any.hpp>
    
    class CVar
    {
    public:
    	std::string name;
    	boost::any var;
    };
    
    class CVarSys
    {
    public:
    	std::vector<CVar*> cvarVec;
    
    	template< class T > void AddCvar( std::string name, T var )
    	{
    		CVar *cvar = new CVar;
    		cvar->name = name;
    		cvar->var = var;
    
    		cvarVec.push_back( cvar );
    	}
    
    	template< class T > T GetCvarValue( std::string name )
    	{
    		for( std::vector<CVar*>::iterator it = cvarVec.begin(); it != cvarVec.end(); it++ )
    		{
    			if( (*it)->name == name )
    			{
    				if( (*it)->var.type().name() == typeid( T ).name() )
    					return boost::any_cast<T>( (*it)->var );
    				else
    				{
    					std::cout << "You want the type " << typeid( T ).name() << " returned but object is " << (*it)->var.type().name() << std::endl;
    				}
    			}
    		}
    
    		return 0;
    	}
    
    	std::string GetTypeOf( std::string name )
    	{
    		for( std::vector<CVar*>::iterator it = cvarVec.begin(); it != cvarVec.end(); it++ )
    		{
    			if( (*it)->name == name )
    				return (*it)->var.type().name();
    		}
    
    		return "ERROR";
    	}
    };
    

    Nun hab ich aber irgendwas gelesen von wegen boost::any und das benutzen von typeid() etc würde auf schlechten Programmierstil hinweisen und dass ich mein Konzept nochmal überdenken sollte.. aber wie würdet ihr sowas schreiben.. Ich suche nur ein paar Anreize für neue Konzepte (;

    Oder würdet ihr es einfach so lassen wie ich es jetzt hab?



  • Hi,

    Wäre typeid bzw. RTTI generell "schlecht", wäre es wohl nicht im C++ Standard drinnen 😉

    Immer dann, wenn man zwingend einen Container (hier vector) mit mehr als nur einem Typ von Variablen füllen möchte braucht man eben eine Klasse, die alle Typen verwalten kann und die jedem Typ eine eindeutige ID zuweist. Die Frage wäre dann eher: Kannst du das Programm auch ohne einen solchen Container umsetzen?

    Dein Beispiel erinnert mich etwas an Konfigurations-Interfaces (konkret COM, VARIANT, OleDB). Während sich die Steuerung einer Komponente oft durch ein Interface gut abstrahieren läßt, ist dies bei speziellen Einstellung, die implementierungsspezifisch sind, allgemein nicht so einfach lösbar.... und dann braucht man eben irgend eine Art von "Property" Klasse, die einen string-Namen mit einen beliebigen Werte-Typ verknüpft.

    Solche Lösungen findest du also auch in anderen Bibliotheken (und bei COM unter Windows ist das fast immer so gelöst ... auch das dotNet framework benutzt dann gerne den Typ "object", welcher dann umgecastet werden muss).

    Natürlich kann man jetzt aber darüber streiten wie "stabil" die typeid Namen sind. Unterschiedliche Compiler nutzen ev. unterschiedliche Namen für typeids ... und wenn du einen Code auf mehrere Bibliotheken verteilst, sollte man zumindest überprüfen, ob typeid die richtige Wahl ist.
    Ich hab mir dafür ein paar eigene templates gebaut, die jedem unterstützten Typ eine interne ID zuweisen und dann eben diese ID für den Vergleich und nicht den typeid-Namen benutzen ... und das klappt ganz gut 😉

    cu xor



  • Die Frage ist also, ob Du mit if/if/if/if/if/.../if oder mit virtual dispatchen willst.

    Wäre typeid bzw. RTTI generell "schlecht", wäre es wohl nicht im C++ Standard drinnen

    Ungutes Argument. Hätte Gott gewollt, daß der Mensch fliegt, hätte er ihm Flügel wachsen lassen.



  • Wurde es nicht mal angesprochen, dass das Weglassen bestimmter Features in einer Sprache mehr Kritik (und schlechtere Workarounds) mit sich bringt als deren Implementierung und Aufnahme in den Standard?

    Leider kommen da oft so pauschale Aussage wie "XYZ ist schlecht", und dem kann ich einfach nicht zustimmen. Auch typeid hat somit seine Existenzberechtung.

    Das heißt aber natürlich nicht, dass man es an allen Stellen hirnlos einsetzen soll ... im Gegenteil.

    Ist doch vergleichbar mit "Exceptions sind sinnlos" oder "dynamic_cast gehört verboten". Das muss man doch alles im Detail betrachten und entscheiden.

    PS:
    Ich persönlich finde im Code [return 0] und [return "ERROR"] schlechter als den Einsatz von typeid ... da gehört ein throw hin - aber ist nur meine Meinung :p



  • Es gibt einige Features im Standard, die nur in Ausnahmefällen eine gute Idee sind. RTTI ist eines davon, goto und mutable zwei weitere. Es ist wichtig zu verstehen, dass ein Standard zu einem guten Teil deskriptiv ist, d.h., dass das festgeschrieben wird, was gängige Implementationen schon machen. Das, was gängige Implementationen vor einer Standardisierung machen, ist nicht immer eine gute Idee.

    RTTI im speziellen ist ein Nebenprodukt bestimmer Implementationen des Exception-Handlings. Wenn man es eh schon hat, hat man sich wohl gedacht, kann man es für andere Dinge zur Verfügung stellen, vielleicht kann's ja mal jemand brauchen. Dass man, wenn man es braucht, im Zweifel ein sehr unsauberes und auf jeden Fall kein objektorientiertes Design hat, steht auf einem anderen Blatt, und ich hätte es eigentlich auch nicht für sinnvoll gehalten, hätte das Standardisierungskomitee sich in die Designpraktiken der Programmierer (ob dreckig oder nicht) eingemischt. Metaprogrammierung wäre dann wohl nie entdeckt worden.

    Was den konkreten Fall angeht, spricht etwas dagegen, für verschiedene Typen verschiedene Container zu benutzen? Statt

    CVarSys sys;
    
    sys.GetCvarValue<int>("foo");
    

    beispielsweise

    CVarSys<int> sys_int;
    
    sys_int.GetCvarValue("foo");
    

    Es hängt allerdings ein bisschen davon ab, an welcher Stelle du weißt, welche Datentypen du letztendlich benötigen wirst.



  • Was haltet ihr denn davon:

    class Event {
      [...]
      virtual bool isInvalidatingOtherEventsOfSameType() const throw() = 0;
    }
    

    Dann gibt es ganz viele spezifische Events, die das als Basisklasse erben. Die Userklasse hat jetzt ne Methode addEvent, die folgendes macht (das auto ist ein c++0x feature, dann spart man sich die manuelle Typisierung):

    if(event->isInvalidatingOtherEventsOfSameType())
    {
      auto &addedType = typeid((*event));
      auto it = events_.begin();
      while(it != events_.end())
      {
        if(addedType == typeid((*(*it))))
        {
          it = events_.erase(it);
        }
        else
        {
          it++;
        }
      }
    }
    

    Es geht darum, dass manche Events mehrfach vorkommen dürfen und manche Events die vorherigen Events ungültig machen.

    Ein Beispiel für ein solches Event wäre die Bewegung eines Users zu einer Zielkoordinate, denn sobald er sich auf eine neue Koordinate bewegt ist das alte Event mit der alten Zielkoordinate natürlich ungültig.

    Ich persönlich bin der Meinung in meinem Usecase ist RTTI die perfekte Lösung für das Problem, oder seht ihr das anders?

    mfg, René~



  • Da keiner widerspricht, gehe ich davon aus, dass mein Usecase eine perfekte Verwendung von typeid darstellt. @OP, du siehst also, typeid ist nicht immer schlechter Stil. Wenn man allerdings nach typeid branched (switch / if..else) dann ist es wahrscheinlich anders besser lösbar.



  • NewSoftzzz schrieb:

    Da keiner widerspricht, gehe ich davon aus, dass mein Usecase eine perfekte Verwendung von typeid darstellt.

    Nein. Ich wollte mir bloß die Mühe nicht machen.
    Statt

    if(event->isInvalidatingOtherEventsOfSameType())
    {
      auto &addedType = typeid((*event));
      auto it = events_.begin();
      while(it != events_.end())
      {
        if(addedType == typeid((*(*it))))
        {
          it = events_.erase(it);
        }
        else
        {
          it++;
        }
      }
    }
    

    denke ich an

    if(event->isInvalidatingOtherEventsOfSameType())
    {
      unique_events[event->getEventClassID()]=event;
    }
    


  • volkard schrieb:

    if(event->isInvalidatingOtherEventsOfSameType())
    {
      unique_events[event->getEventClassID()]=event;
    }
    
    1. Die Events haben keine Id, da unnötiger RAM Verbrauch
    2. Die Events sind in einer std::list, da eine std::map unnötigen RAM Verbrauch bedeutet und ich außer beim hinzufügen von unique Events immer linear über alle Events iterieren muss.
    3. Welche Nachteile birgt meine typeid Lösung?

    PS: Ist eventClassId hier eine static methode die eine ID der Klasse darstellt? Wo ist da der Unterschied zu typeid? O_o

    mfg, René~



  • NewSoftzzz schrieb:

    1. Die Events haben keine Id, da unnötiger RAM Verbrauch

    Ich rede von Klassen-IDs. Auch kein RAM-Verbrauch.

    NewSoftzzz schrieb:

    1. Die Events sind in einer std::list, da eine std::map unnötigen RAM Verbrauch bedeutet und ich außer beim hinzufügen von unique Events immer linear über alle Events iterieren muss.

    Ich rede von einem Array. Daher viel weniger RAM-Verbrauch als std::list und auch viel schneller.

    NewSoftzzz schrieb:

    1. Welche Nachteile birgt meine typeid Lösung?

    Weiß nicht. Ich finde sie nicht naheliegend. Du sagst es gibt events, die ihre Verwandten automatisch töten. Ich will eher sowas wie es gibt events, die nur einmal vorkommen können.

    NewSoftzzz schrieb:

    PS: Ist eventClassId hier eine static methode die eine ID der Klasse darstellt? Wo ist da der Unterschied zu typeid? O_o

    Vielleicht kein großer. Aber wenn man schonmal so weit ist, kann man das Array auch auffächern in pro Eventklasse einen static pointer auf das unique event.
    Ich nehme an, da geht noch einiges.



  • Ach so, jetzt versteh ich das. Findest du es wirklich sinnvoll, wenn alle Algorithmen linear über die Events iterieren, die dann in mehrere Container zu verteilen? Dadurch würden die Algorithmen unnötig komplizierter.

    Desweiteren ist die Reihenfolge in der Liste äquivalent zur chronologischen Entstehung der Events, das würde durch deine Aufsplittung auch verloren gehen.

    Die Sache mit den unique Events ist eher ein Schutzmaßnahme falls die Events noch nicht vom Client abgeholt wurden und stellt keinen Regelfall dar.

    mfg, René~



  • NewSoftzzz schrieb:

    Ach so, jetzt versteh ich das. Findest du es wirklich sinnvoll, wenn alle Algorithmen linear über die Events iterieren, die dann in mehrere Container zu verteilen? Dadurch würden die Algorithmen unnötig komplizierter.

    Das eine schließt das andere nicht aus. Alle events liegen in einer (chronologischen?) Liste. Und trotzdem wird pro Eventklasse, von denen es nur eins geben kann, der unique pointer (vielleicht als list::iterator) gehalten.

    Uups, ich denke hier gleich an einen intrusiven doppelt verketten Ring denke, und normale Zeiger.



  • Mal ne Frage zwischendurch: Warum sollte man ne Liste der gefangenen Events, die noch nicht veraltet sind, speichern wollen?



  • @Volkard: Nun ja, dein Array wäre auch schon RAM Verbrauch, da zu jedem Zeitpunkt ca. 98% der User gar keine Events haben. Wenn jetzt jeder von denen so ein Array mit allen möglichen unique Events mitschleppen müsste...

    Michael E. schrieb:

    Mal ne Frage zwischendurch: Warum sollte man ne Liste der gefangenen Events, die noch nicht veraltet sind, speichern wollen?

    Das sind Ajax Events, die natürlich solange gespeichert werden bis der Client sie abholt oder sie eben ablaufen.

    mfg, René~



  • NewSoftzzz schrieb:

    @Volkard: Nun ja, dein Array wäre auch schon RAM Verbrauch, da zu jedem Zeitpunkt ca. 98% der User gar keine Events haben. Wenn jetzt jeder von denen so ein Array mit allen möglichen unique Events mitschleppen müsste...

    An wieviele unique-Typen denkst Du denn?
    Ein Server für viele Ajax-Clients also, der soo viele Clients hat, daß der Speicher ausgeht. Ist deine allocation granularity eigentlich 32 Byte? Sind das schon so viel wie ein Array mit 8 Zeigern?
    Die Schleife fühlt sich für mich einfach nicht gut an. Da bin ich verbohrt.



  • volkard schrieb:

    An wieviele unique-Typen denkst Du denn?

    Keine Ahnung, aber es werden immer mehr.

    volkard schrieb:

    Ein Server für viele Ajax-Clients also, der soo viele Clients hat, daß der Speicher ausgeht.

    Ich rechne immer mit 10 millionen. Im Moment lad ich alle in den RAM ob off- oder online. Das werd ich aber eventuell irgendwann noch ändern müssen 😉

    volkard schrieb:

    Ist deine allocation granularity eigentlich 32 Byte? Sind das schon so viel wie ein Array mit 8 Zeigern?

    Ein Zeiger hat 8 byte (64 bit).

    @Schleife: Im Regelfall liegen in der Liste 0-3 Events.

    mfg, René~



  • NewSoftzzz schrieb:

    Ich rechne immer mit 10 millionen. Im Moment lad ich alle in den RAM ob off- oder online. Das werd ich aber eventuell irgendwann noch ändern müssen 😉

    Die offliners brauchen auch eine Event-Liste?



  • volkard schrieb:

    Die offliners brauchen auch eine Event-Liste?

    Korrektur: Ja, weil Events auftreten können und abgeholt werden können beim relogin.

    mfg, René~



  • Dem habe ich dann nichts mehr hinzuzufügen.



  • typeid() ... schlechter Stil?

    Ja...


Log in to reply