Lange Select-Statements vermeiden?



  • Nimm doch std::map.
    Die Id ist der Schlüssel, der Value die Funktion. Der Schlüssel kann dann ein int oder etwas komplexeres sein, z.B. ein std::string.



  • Ich hab das jetzt entsprechend euren Ratschlägen mit einer map so gemacht:

    template<typename _key, typename _fn>
    class FnMap
    {
    public:
    	void reg(_key i, _fn f)
    	{
    		fns.insert( std::make_pair(i, f) );
    	}
    
    	_fn operator()(_key i)
    	{
    		return fns[i];
    	}
    private:
    	std::map<_key, _fn > fns;
    };
    
    FnMap<int, boost::function<void (std::string)> > fm;
    fm.reg(1, &foo);
    fm.reg(999, &bar);
    // ...
    while(receive telegram)
    	fm(telegram.id)(telegram.rest);
    

    Das ist schon viel schöner, weil ich an einer Stelle wo es nicht stört alle Funktionen registrieren kann - und die eigentliche Funktion mit dem Empfang usw. bleibt schön sauber.
    Das einzige was mir jetzt noch aufstößt ist dieser hässliche Aufruf mit den zwei Klammern. Ich hätte statt fm(id)(parameter, ...); lieber fm(id, parameter, ...);.
    Ich denke mal das geht bestimmt, aber da ich das ganze auch an anderer Stelle einsetzen möchte, wo es dann mehrere Parameter geben könnte, weiß ich absolut nicht wie. Kann mir da jemand auf die Sprünge helfen?



  • Moin

    was mir auffällt, was passiert wenn du 2 mal die gleiche funktions nummer fersuchst zu registrieren?

    was passiert, wenn für den key kein eintrag existiert?

    ist ggf der Array operator etwas für dich? sieht aber auch blöd aus.

    ein 3. parameter für das Template, das die Parameter art angibt?
    und anstelle des () operators eine Methode execute( <_key>, <_param> )
    Mehrere parameter funktionieren dann nicht als parameter, aber darür gibts strukturen.
    ggf ein macro für die template instanzierung definieren. ( zwgs fehler _fn und _param)

    ggf währe ein default funktion für alle nicht registrerten funktionen sinnvoll.

    gruss



  • Du kannst den operator() mit beliebig vielen Parametern erzeugen (auch zusätzlich zum anderen operator()):

    void operator()(_key i, const std::string &s)
    {
       fns[i](s);
    }
    

    Hier könntest (und solltest) du dann auch noch die Fehlerabfrage einbauen (wie von Termite vorgeschlagen).

    P.S. Wenn du unterschiedliche Maps dieser Art benötigst, solltest du den Parameter "std::string" zum Template hinzufügen (bzw. diesen Typ sowie den Rückgabetyp aus boost::function ermitteln).



  • switch ist im Prinzip schon das richtige, Optimierungen wie Sprungtabelle etc. kann der Compiler auch sehr gut selbst machen (wahrscheinlich sogar besser als du).

    Wie wärs denn mit ein wenig Präprozessor(=Schwarzer) Magie?

    #include <boost/preprocessor/repetition/repeat_from_to.hpp>
    
    #define CASE(z, n, text)                  \
        case n:                               \
            handle_telegram< n > (telegram);  \
            break;
    
    template <int>
    void handle_telegram (telegram_type const&) {}
    
    // Spezialisieren für die jeweiligen Telegrammtypen (definiert durch eine 
    // Ganzzahlkonstante), gerne auch benannt, zB TELEGRAM_*
    // So in etwa:
    const int TELEGRAM_WAR_WAS_BEGINNING = 0x42;
    
    template <>
    void handle_telegram<TELEGRAM_WAR_WAS_BEGINNING> (telegram_type const&)
    {
        std::cout << "A.D. 2101\nwar was beginning!";
    }
    
    // Dann das hier in der Funktion, die die Telegramme verarbeitet:
    switch (telegram.id)
    {
    BOOST_PP_REPEAT_FROM_TO(0, 999, CASE, )
    }
    

    /edit: Hab den Code und die Kommentare etwas geändert. Jetzt klarer, Termite?



  • was mir auffällt, was passiert wenn du 2 mal die gleiche funktions nummer fersuchst zu registrieren?

    Das wird einfach nicht ausgeführt, da könnte ich noch ein assert reinmachen, stimmt. Ist ja wahrscheinlich eine Funktion zweimal reingepackt.

    was passiert, wenn für den key kein eintrag existiert?

    Dann kommt eine exception. Ich bin mir noch nicht ganz sicher ob ich das nicht sogar gut fände - muss ich nochmal drüber nachdenken.

    @.filmor:
    Uiui - Präproz.Magie ist mein Einhorn. Kann der Compiler wirklich große switch-statements optimieren? Wäre wohl anzunehmen, das stimmt... Ich _versuchs_ mal 🙂



  • @.filmor

    Irgendwie komm ich mit dem Template nicht klar.

    zeile 8 und 9 definieren doch ein Funktions template. oder lieg ich da falsch.

    in zeile 5 wird dieses Funktions template aufgerufen. quasi an den tausend stellen im switch case. Wozu die <n> bei einem funktions template?

    wo befindet sich die implementierung der funktionen?

    gruss.



  • n Template-Spezialisierungen, weil du n case Label hast. Du willst ja jeden Fall unterschiedlich behandeln.

    Die Implementierung der Spezielisierungen musst du schon selber machen.

    Die gehören hier hin:

    // Spezialisieren für die jeweiligen Typen, gerne auch mit benannten Konstanten
    // zB TELEGRAM_*
    

    Gruß
    Don06



  • So, ich habe jetzt beide Methoden ausprobiert. Der Code ist wesentlich kürzer als mit ausgeschriebenem switch-case, wobei ich auch bei der Methode mit der Map einiges an Schreibarbeit mit BOOST_PP_Zauberei abgekürzt habe. Was mich überrascht hat ist der doch drastische Geschwindkeitsunterschied: Die Map ist mit gcc3.4.5 und -O3 fast doppelt so schnell (Das Testprogramm hat 200 case-fälle) 😮 Mit einem anderen Compiler (MSVC und gcc4.3) will ich das aber nochmal nachtesten.
    Nur mal Falls es jemanden interessiert.

    Edit: Tja, das kommt dabei raus wenn man nicht lesen kann (was man selbst ausgeben lässt). Es verhält sich genau anders rum, switch ist schneller als map 🙄



  • SIEG!!11einself


Anmelden zum Antworten