Macros



  • Danke für eure Antworten 🙂
    Jetz verstehe ich wenigstens Sinn und Zweck dieser Makros.



  • volkard schrieb:

    abgesehen vom genannten, wobei das von Kleiner Troll auch ohne makro geht

    Hm? Meinst du einfach austippen? Ja, geht. Oder hast du eine andere Lösung im Kopf?



  • Kleiner Troll schrieb:

    volkard schrieb:

    abgesehen vom genannten, wobei das von Kleiner Troll auch ohne makro geht

    Hm? Meinst du einfach austippen? Ja, geht. Oder hast du eine andere Lösung im Kopf?

    Was spricht gegen:

    template<typename Function, typename T>
    void Register(Function function, T const& name) {
      Register_helper(function, SomeTemplate::MyTemplate<Function>, name);
    }
    

    wobei mir hier der Sinn komplett unklar ist.

    oder meintest du statt

    #define REGISTER_MAKRO(function, name) \
            Register(function, SomeTemplate::MyTemplate<function>, name)
    

    doch

    #define REGISTER_MAKRO(function, name) \
            Register(#function, SomeTemplate::MyTemplate<function>(), name)
    

    ??



  • Kleiner Troll schrieb:

    volkard schrieb:

    abgesehen vom genannten, wobei das von Kleiner Troll auch ohne makro geht

    Hm? Meinst du einfach austippen? Ja, geht. Oder hast du eine andere Lösung im Kopf?

    Du hast nicht klargemacht, was

    Register(function,SomeTemplate::MyTemplate<function>,name);
    

    tun soll.
    Aber so frißt es mein Compiler nicht, fürchte ich. Der zweite Parameter scheint nur ein Typ zu sein.



  • Manchmal können Macros einen einfach Tipparbeit abnehmen. Das DRY-Prinzip kann bis zu einem gewissen Grad auch hier agewendet werden. Kleines Beispiel, das ich benutze um einen enum-Wert auf einen Typen zu mappem:

    template <CommandID ID>
    struct CommandID2Type
    {
      typedef EmptyType type;
    };
    
    #define MAPCOMMANDID(id, mappedtype) \
                                         \
    template <>                          \
    struct CommandID2Type<id>            \
    {                                    \
      typedef mappedtype type;           \
    }
    
    MAPCOMMANDID(ERROR, std::string);
    MAPCOMMANDID(OUTPUT, std::string);
    MAPCOMMANDID(PLAYER_INFO_OPEN, std::vector<OpenPlayerInfo>);
    MAPCOMMANDID(TABLE_INFO, TableInfo);
    MAPCOMMANDID(TURN_INFO, TurnInfo);
    
    MAPCOMMANDID(PLAY_CARD, CardID);
    MAPCOMMANDID(SETUP_PLAYERS, std::vector<std::string>);
    #undef MAPCOMMANDID
    

    jedesmal das stuct komplett auszuschreiben wäre doch ein wenig länglich, so sieht man sofort was passiert, und die Wirkunsweise ist dank #undef begrenzt.



  • Das ganze ist ein bisschen Ansichtssache - man kann mit Makros viel Schindluder treiben, aber das gilt für so ziemlich alles.

    Mein Standpunkt ist, dass man Makros mit Vorsicht behandeln sollte, weil es sehr leicht ist, damit zu verschleiern, was eigentlich passiert, wodurch der Code sehr unübersichtlich werden kann, es allerdings Zusammenhänge gibt, in denen Makros schlimmeres verhindern - beispielsweise, den selben Kram zwanzigtausendmal zu schreiben, etwa, wenn dieser sich nicht in eine Funktion auslagern lässt. Das muss dann im Einzelfall entschieden werden.

    Konkretes Beispiel: Man schreibt ein C-API um eine C++-Kernbibliothek. Das C-API kennt keine Exceptions, also müssen die vorher abgefangen werden, und häufig in einem solchen Fall auf die gleiche Art und Weise. Da catch-Blöcke sich nicht in Funktionen auslagern lassen, besteht die Wahl bei einem ausreichend großen API dazwischen, die selben zehn oder so Zeilen in jeder Funktion wieder zu copy&pasten oder ein Makro zu schreiben - in so einem Zusammenhang ist ein Makro meines Erachtens nach durchaus vertretbar. Ich berufe mich hier auf das DRY-Prinzip.

    Ansonsten gibt es gelegentlich zwischen verschiedenen Compilern Unterschiede in der...ich nenne es mal Auslegung von Standards, für die von Janjan genannten Feature-Makros nicht ausreichen (bzw. bei denen die Umsetzung mit nur diesen Feature-Makros eher unschön ist). Etwa benutze ich für TR1-Header folgendes Konstrukt:

    #ifdef __GNUC__
    #  define TR1_HEADER(header) <tr1/header>
    #else
    #  define TR1_HEADER(header) <header>
    #endif
    
    #include TR1_HEADER(memory)
    #include TR1_HEADER(functional)
    // ...
    
    #undef TR1_HEADER
    

    Ansonsten kann ich volkard nur zustimmen. Grundlegend ist das relevante Entscheidungskriterium Übersichtlichkeit. Wenn ich zwei Möglichkeiten zur Auswahl habe, von denen eine übersichtlich und eine unübersichtlich ist, nehme ich die unübersichtliche, völlig egal, welche Sprachmittel ich dazu benutzen muss.



  • function ist ein Funktionszeiger mit einer gewissen signatur a, name ist ein std::string, das template hat eine signatur b != a (der einzige Zweck des templates ist genaugenommen die Signatur zu ändern)
    Register nimmt nimmt einen Funktionszeiger mit Signatur a, einen mit b, und einen Namen, unter dem es die Funktion unter diesem Namen als callback an 2 voneinander unabhängigen Stellen registriert.

    Zu Shade of Mine:

    function, SomeTemplate::MyTemplate<Function>, name

    und

    function, SomeTemplate::MyTemplate<function>, name

    Ist nicht dasselbe 😉



  • #include <cstdio>
    #include <cstring>
    #include <string>
    
    void oldFunc(const char *str, int len)
    {
    	printf("%s, len %i\n", str, len);
    }
    
    #define SHORTER_C_FUNC(x) oldFunc(x, strlen(x))
    
    inline void betterCppFunc(const std::string& str)
    {
    	SHORTER_C_FUNC(str.c_str());
    }
    
    inline void betterCppFunc(const char *str)
    {
    	SHORTER_C_FUNC(str);
    }
    
    int main()
    {
    	std::string test1 = "bla";
    	char test2[] = "test";
    
    	betterCppFunc(test1);
    	betterCppFunc(test2);
    }
    


  • seldon schrieb:

    Ansonsten kann ich volkard nur zustimmen. Grundlegend ist das relevante Entscheidungskriterium Übersichtlichkeit. Wenn ich zwei Möglichkeiten zur Auswahl habe, von denen eine übersichtlich und eine unübersichtlich ist, nehme ich die unübersichtliche, völlig egal, welche Sprachmittel ich dazu benutzen muss.

    Das ehrt Dich. Aber ich bin um ehrlich zu sein, nicht so fleißig und nehme gelegentlich einfach die übersichtlichere.



  • Äh...Tipfeeler. 😮



  • Kleiner Troll schrieb:

    function ist ein Funktionszeiger mit einer gewissen signatur a, name ist ein std::string, das template hat eine signatur b != a (der einzige Zweck des templates ist genaugenommen die Signatur zu ändern)
    Register nimmt nimmt einen Funktionszeiger mit Signatur a, einen mit b, und einen Namen, unter dem es die Funktion unter diesem Namen als callback an 2 voneinander unabhängigen Stellen registriert.

    Soll Register auch ein Makro sein? Falls ja, war Dein Posting ein schwaches Argument.
    Fall nein, zeige eine einfache Beispielimplementierung für Register.
    So glaube ich noch nicht, daß die Anforderung möglich war.



  • int Singleton::Register(SignaturEins function, SignaturZwei cFunction, const string& name)
    {
    	if (Register(function, name) != -1)	{ 
    		Register(cFunction, name);  
    		return 0;
    	} else {
    		return -1;
    	}
    }
    

    Im echten Beispiel sind "SignaturEins/Zwei" einfach typedefs für einen Funktionszeiger.
    Ich hoffe du hast nichts dagegen das ich die Namen etwas generalisiert habe.



  • Das template könnte als Beispiel so aussehen:

    template<SignaturEins T> int MyTemplate(int Zahl)
    {
    std::vector<int> tmp;
    tmp.push_back(Zahl);
    return T(tmp);
    }



  • Compilieren sollte das Programm schon.



  • Ach, du willst ein kompilierbares minimalbeispiel? Sag das doch gleich :>
    Warte 10 min, ich stells zusammen.



  • #include <vector>
    #include <string>
    #define REGISTER_MAKRO(function, name) \
            Register(function, MyTemplate<function>, name)
    using namespace std;
    
    typedef int (*SigEins)(vector<int>);
    typedef int (*SigZwei)(int);
    
    template<SigEins T> int MyTemplate(int Zahl)
    {
       vector<int> tmp;
       tmp.push_back(Zahl);
       return T(tmp);
    }
    
    int Register(SigEins func, const string& name)
    {
       //hier gehts weiter zum registrieren in die eine Richtung, wies genau geht sollte irrelevant sein
       return 0; //wir gehen davon aus das das registrieren hier erfolgreich war
       //return -1; //das wird zurückgegeben falls es nicht erfolgreich war
    }
    
    int Register(SigZwei func, const string& name)
    {
       //hier gehts weiter zum registrieren in die andere Richtung, wies genau geht sollte irrelevant sein
       return 0;
    }
    
    int Register(SigEins function, SigZwei cFunction, const string& name)
    {
        if (Register(function, name) != -1)    {
            Register(cFunction, name);  
            return 0;
        } else {
            return -1;
        }
    }
    
    int Test(vector<int>)
    {
       return 2;
    }
    
    int main()
    {
       REGISTER_MAKRO(&Test, "test");
    }
    


  • Noch gehts.

    #include <vector>
    #include <string>
    
    using namespace std;
    
    typedef int (*SigEins)(vector<int>);
    typedef int (*SigZwei)(int);
    
    template<SigEins T> int MyTemplate(int Zahl)
    {
       vector<int> tmp;
       tmp.push_back(Zahl);
       return T(tmp);
    }
    
    int Register(SigEins func, const string& name)
    {
       //hier gehts weiter zum registrieren in die eine Richtung, wies genau geht sollte irrelevant sein
       return 0; //wir gehen davon aus das das registrieren hier erfolgreich war
       //return -1; //das wird zurückgegeben falls es nicht erfolgreich war
    }
    
    int Register(SigZwei func, const string& name)
    {
       //hier gehts weiter zum registrieren in die andere Richtung, wies genau geht sollte irrelevant sein
       return 0;
    }
    
    int Register(SigEins function, SigZwei cFunction, const string& name)
    {
        if (Register(function, name) != -1)    {
            Register(cFunction, name);
            return 0;
        } else {
            return -1;
        }
    }
    
    int Test(vector<int>)
    {
       return 2;
    }
    
    #define REGISTER_MAKRO(function, name) \
            Register(function, MyTemplate<function>, name)
    
    //alles oberhalb unveränder, außer das makro runtergezogen zum besser 
    //anschauen können, während ich das template zusammenfrickle. 
    
    template<SigEins FUNC>
    void REGISTER_TEMPLATE(const string& name){
    	Register(FUNC,MyTemplate<FUNC>,name);
    }
    
    int main()
    {
       REGISTER_MAKRO(&Test, "test");
       REGISTER_TEMPLATE<&Test>("test");
    }
    

    Wenn die function auch andere Signaturen als SigEins haben können soll, wird der Aufruf in der main aber schon häßlicher. Da mache ich mich dann aber auch locker und denke an Makros.

    Aber spaßig wird's mit

    #define REGISTER_MAKRO2(function) \
            Register(function, MyTemplate<function>, #function)
    
    int main()
    {
       REGISTER_MAKRO(&Test, "test");
       REGISTER_TEMPLATE<&Test>("test");
       REGISTER_MAKRO2(Test);
    }
    


  • Stimmt, hast mich überzeugt, geht.

    Ich finde zwar immer noch die Verwendung des Makros sieht schöner aus (da es ähnlicher an dem Funktionsaufruf ist den es eigentlich darstellt), aber wegen den üblichen Problemen mit Makros sollte man es evtl lassen. Die ganze Verwendung in einem Projekt nur wegen sowas zu ändern würde ich allerdings nicht :>



  • Janjan schrieb:

    Makros sind in C++ nur für Include-Guards und bedingte Kompilation gut.

    Nicht ganz. Alleine schon wenn man an Dinge wie assert oder BOOST_FOREACH denkt.

    Es gibt auch sonst ab und zu Situationen, in denen Makros sehr praktisch sind. Nur weil man etwas viel hässlicher ohne sie erledigen kann, ist das kein Grund, auf sie zu verzichten. Ihre Einsatzgebiete reichen von einfachen Textersetzungen bis zu mächtigen Codegeneratoren. Die Möglichkeiten sollte man nicht leichtfertig unterschätzen.

    Davon abgesehen war es noch nie vorteilhaft, Sprachmittel aus C kategorisch schlecht zu finden.


Anmelden zum Antworten