Makros?



  • Hallo!

    Stimmt in C++ sollte man Makros nicht unbedingt benutzen. Sie koennen zu Fehlern fuehren die nur schwer zu finden sind.

    Du solltest also die Finger davon lassen.

    Es kann jedoch nicht schaden wenn du dir mal ansiehst wie sie funktionieren, und auch ein paar Uebungen dazu machen. Solange sie nicht zu einer gewohnheit werden.

    mfg LastManStanding



  • Siege schrieb:

    Hallo, Ich habe ein paar Fragen zu Makros.
    In der FAQ hier steht leider noch nichts darüber drinnen... in 2 Tutials, die ich z.Z. mache steht entweder nichts oder nur wie man sie definiert... 😕

    Ich habe bis jetzt nur gelesen dass man Markos in C++ nicht benutzen soll!

    1. Braucht man als Anfänger Makros?
    2. Wenn ja wann brauche ich sie?

    Und das wars eigentlich auch schon. Ich hoffe ihr könnt mir helfen! 😕

    Tja, ist sicherlich mal wieder das Tutorial vom Stefan Zerbst. Der hat es ja bis Heute noch nicht gerallt wieso man Konstanten benutzen sollte und kein #define!

    Tja ja... er muss ja umbedingt seine Dummheit an andere "Weiterlehren". 👎



  • Ok, sehr gut genau das wollte ich hören ^^

    Dann lasse ich jetzt erst mal ganz die Finger weg von Makros und schau mir das später genauer an...

    mfg

    Siege

    edit: ihr könnt den thread ja jetzt in die FAQ stecken...



  • Maestro de Merde schrieb:

    Tja, ist sicherlich mal wieder das Tutorial vom Stefan Zerbst. Der hat es ja bis Heute noch nicht gerallt wieso man Konstanten benutzen sollte und kein #define!

    Tja ja... er muss ja umbedingt seine Dummheit an andere "Weiterlehren". 👎

    Ich dachte in den Tutorials stünde nichts über Macros drin? Wie kann man sie dann an stelle von const benutzen? 😕

    Jedenfals benutzt jedes Program Macros. Jeder Header ist mit Includeguards geschützt:

    #ifndef HEADER_H
    #define HEADER_H
    //...
    #endif
    

    Wenn ich nun

    #include"header.h"
    #include"header.h"
    

    Schreibe dann wird der Header nur einmal eingefügt da er das zweitemal wieder von den Macros rausgehaut wird.

    Auch sonst machen Macros hin und wieder Sinn. Wenn sie verhindern, dass man unnötig Code dubliziert. Zum Beispiel jede in einer Klassenhireachie braucht default Methoden a la clone. Entwerden einmal mit einem Macro definieren oder in jeder Klasse. Wenn es 30 Klassen und mehr geworden sind und nun noch eine weitere Methode hinzugefügt werden soll, dann ist man froh die Macroversion genommen zu haben.

    Macros machen allerdings keinen Sinn als Konstanten- oder Funktionsersatz.



  • @Ben04
    inkludeguards != makros



  • Ben04 schrieb:

    Maestro de Merde schrieb:

    Tja, ist sicherlich mal wieder das Tutorial vom Stefan Zerbst. Der hat es ja bis Heute noch nicht gerallt wieso man Konstanten benutzen sollte und kein #define!

    Tja ja... er muss ja umbedingt seine Dummheit an andere "Weiterlehren". 👎

    Ich dachte in den Tutorials stünde nichts über Macros drin? Wie kann man sie dann an stelle von const benutzen? 😕

    Nicht an Stelle von const im Allgemeinen, sondern an Stelle von Konstanten.

    #define BLUB 100
    // oder besser
    const int BLUB = 100;
    


  • Ben04! Die Include-Guards sind ja auch eher ne Ausnahme... halt weil es in dem Fall wirklich und vorallem leider nicht anders geht. Ist halt historisch bedingt.

    Aber es gibt ja auch schon Alternativen, die ein paar Compiler anbieten. Z.B. das "#pragma once" als Includeguard.

    Wobei ich mich frage, warum die Compiler bei der heutigen Rechenleistung und Speicherausbau nicht einfach selber schauen ob sie die Header-Datei nicht schon mal includiert haben. 😡



  • Artchi schrieb:

    Wobei ich mich frage, warum die Compiler bei der heutigen Rechenleistung und Speicherausbau nicht einfach selber schauen ob sie die Header-Datei nicht schon mal includiert haben. 😡

    Vielleicht, weil ihnen dann ein paar Puristen vorwerfen würden den ANSI-Standard mit Füßen zu treten.



  • Artchi schrieb:

    Ben04! Die Include-Guards sind ja auch eher ne Ausnahme...

    Nee. Die Include-Guards sind wie +++++++++++++++++++++++ schon schrieb keine Makros. Das ist einfach nur bedingte Kompilierung. Bedingte Kompilierung ist sogar in C# erlaubt. Makros sind #define-Direktiven, die beim Auffinden durch den Preprozessor expandiert (deswegen Makro) werden. Das Verwirrende beim Missbrauch von Makros als inline Funktionen sind übrigens hauptsächlich Inkrement- bzw. Dekrement-Anweisungen, da diese evtl. wegen des Ersetzens mehrfach ausgeführt werden. Beispiel:

    #include <iostream>
    
    using namespace std;
    
    #define DOPPELT(x)((x)+(x))
    
    int main()
    {
        int i=2;
        cout << DOPPELT(++i) << "\n" << i << endl;
        cin.get();
    }
    

    Bei Konstanten sind es die Fehlermeldungen, die einem nicht sagen können, wie denn der Name der Konstante lautet, da dieser vom Preprozessor ersetzt wurde und dem Compiler daher nicht bekannt ist.



  • nillable schrieb:

    Nee. Die Include-Guards sind wie +++++++++++++++++++++++ schon schrieb keine Makros.

    Falsch, oder was ist für dich der grundlegende Unterschied hierbei

    #define HEADER_H
    #define BLUBB 0
    

    ?

    Ben04 schrieb:

    Auch sonst machen Macros hin und wieder Sinn. Wenn sie verhindern, dass man unnötig Code dubliziert. Zum Beispiel jede in einer Klassenhireachie braucht default Methoden a la clone. Entwerden einmal mit einem Macro definieren oder in jeder Klasse. Wenn es 30 Klassen und mehr geworden sind und nun noch eine weitere Methode hinzugefügt werden soll, dann ist man froh die Macroversion genommen zu haben.

    Dass Makros durchaus sinnvoll sein können stimmt, aber für dein Beispiel mit Sicherheit nicht. Da gibt es bessere Möglichkeiten.

    Siege schrieb:

    1. Braucht man als Anfänger Makros?

    Nein. (man sollte lediglich wissen, was Include Guards sind und wofür man diese braucht)

    Siege schrieb:

    2. Wenn ja wann brauche ich sie?

    Brauchen ist natürlich relativ, in C++ führen nunmal viele Wege nach Rom.

    - bedingte Compilierung, zB

    #ifdef MAC
        const int noob_factor = 10;
    #else
        const int noob_factor = 5;
    #endif
    

    - Präprozessor Erweiterung nutzen, zB

    #define WIDEN(x) L##x
    #define LOG(x) std::clog << x << ", " << __FILE__ << ", " << __LINE__ << std::endl
    

    Man könnte jetzt meinen, LOG sollte besser als inline Funktion definiert werden, was grundsätzlich ja auch richtig ist. Nur macht das hier wenig Sinn, weil __FILE__ und __LINE__ dann unbrauchbar werden.

    - manchmal kann es auch sinnvoll sein, Ausdrücke bereits vom Präprozessor in Text umwandeln zu lassen

    #define AS_TEXT_(x) #x
    #define AS_TEXT(x) AS_TEXT_(x)
    

    So vielmehr sinnvolle Anwendungen fallen mir momentan nicht ein. 🙂



  • groovemaster schrieb:

    Falsch, oder was ist für dich der grundlegende Unterschied hierbei

    #define HEADER_H
    #define BLUBB 0
    

    ?

    Wenn der Präprozessor BLUBB im Code findet ersetzt bzw. EXPANDIERT (im Sinne von Makro) er es. Bei HEADER_H tut er es nicht. Folgendes kompiliert zum Beispiel genau deswegen nicht:

    #include <iostream>
    using namespace std;
    
    #define FOO
    
    int main()
    {
        cout << FOO << endl; //Fehler: "expected primary-expression"
        cin.get();
    }
    

    //edit:
    Ich muss mich korrigieren, der Mechanismus des Präprozessors ist in beiden Fällen der gleiche, nur dass bei sowas wie HEADER_H bzw. FOO das Vorkommen im Code durch nichts ersetzt (gelöscht) wird. Das Folgende kompiliert zum Beispiel ohne Probleme:

    #include <iostream>
    
    using namespace std;
    
    #define FOO
    
    int main()
    {
        cout << FOO "Hallo" << endl;
        cin.get();
    }
    


  • groovemaster schrieb:

    Dass Makros durchaus sinnvoll sein können stimmt, aber für dein Beispiel mit Sicherheit nicht. Da gibt es bessere Möglichkeiten.

    Na dann bin ich mal gespannt wie du hierzu eine Alternaitve basteln willst die genau so flexibel ist.

    class Base
    {
    public:
      Base*clone()const = 0;
    };
    #define DEFAULT_BASE_METHODS(class)\
      Base*clone()const{return new class(*this);}
    
    class Derived:public Base
    {
    public:
      Derived(int);
      DEFAULT_BASE_METHODS(Derived)
    };
    
    class Derived2:public Derived
    {
    public:
      Derived2():Derived(5){}
      DEFAULT_BASE_METHODS(Derived2)
    };
    

    Es muss nachher möglich sein durch das bloße Verändern der Basisklasse und des Macroersatzes neue Methoden hinzuzufügen.

    groovemaster schrieb:

    - Präprozessor Erweiterung nutzen, zB

    #define WIDEN(x) L##x
    #define LOG(x) std::clog << x << ", " << __FILE__ << ", " << __LINE__ << std::endl
    

    Man könnte jetzt meinen, LOG sollte besser als inline Funktion definiert werden, was grundsätzlich ja auch richtig ist. Nur macht das hier wenig Sinn, weil __FILE__ und __LINE__ dann unbrauchbar werden.

    Wie wäre es mit

    template<class T>
    inline void log(const T&t, const char*var_name, const char*file, unsigned line)
    {
      std::clog<<var_name<<" = "<<t<<", "<<file<<", "<<line<<std::endl;
    }
    #define LOG(x) log(x, #x, __FILE__, __LINE__)
    

    Wenn es denn wirklich ohne inline Funktion gehen soll dann wenigstens in ((foo,(void)0)) packen und (x) bitte. Beispiel:

    #define LOG(x) ((std::clog << (x) << ", " << __FILE__ << ", " << __LINE__ << std::endl, (void)0))
    

    Ersters würde ich bevorzugen, weil es bessere Compilerfehlermeldungen ausspuckt.

    EDIT:

    nillable schrieb:

    Wenn der Präprozessor BLUBB im Code findet ersetzt bzw. EXPANDIERT (im Sinne von Makro) er es. Bei HEADER_H tut er es nicht. Folgendes kompiliert zum Beispiel genau deswegen nicht:

    Im Grunde genommen hast du ja recht, der Präprozessor expandiert nichts da der Macroname größer ist als sein Ersatz. 🕶

    Aber jetzt einmal ehrlich

    #define BLUB
    void BLUB foo BLUB ( BULB int BLUB a, char BLUB b) BLUB{}
    

    ist korrekter Code.



  • nillable schrieb:

    Ich muss mich korrigieren, der Mechanismus des Präprozessors ist in beiden Fällen der gleiche, nur dass bei sowas wie HEADER_H bzw. FOO das Vorkommen im Code durch nichts ersetzt (gelöscht) wird.

    Vollkommen richtig, deshalb bleiben beide Ausdrücke trotzdem Makros.
    Und dass

    cout << FOO << endl;
    

    nicht kompiliert, liegt nicht daran, dass FOO kein Makro ist, sondern dass

    cout <<  << endl;
    

    ganz einfach syntaktisch falsch ist.

    Ben04 schrieb:

    Na dann bin ich mal gespannt wie du hierzu eine Alternaitve basteln willst die genau so flexibel ist.

    Schon mal was von Templates gehört? 😉
    Ausserdem gibts noch ein anderes Problem. Der Präprozessor kennt keine Namensräume. Was ist, wenn du Fremdcode nutzt und jemand hatte dort die gleiche Idee wie du? Das Problem besteht zwar grundsätzlich, ist aber nur ein weiterer Punkt, warum man Makros nur dann nutzen sollte, wenns wirklich nicht anders machbar ist.
    Du solltest dir deshalb solche Spielereien abgewöhnen, ansonsten landest du irgendwann bei BOOST_FUNCTION_..., wo Code Lesen zur Tortur wird. In den seltensten Fällen ist sowas wirklich rentabel. Programmier deine Member-Funktionen schön nach dem WYSIWYG Prinzip 🙂 , dann weiss man sofort, was los ist (oder du sorgst dafür, dass man deine Header nicht findet und deine Doku immer aktuell ist 😃 ). Wenn deine Member-Funktionen von irgendwo anders ihre Funktionalität erhalten, dann nutze entsprechende Design Patterns, wie zB Policies.

    Ben04 schrieb:

    Wie wäre es mit

    template<class T>
    inline void log(const T&t, const char*var_name, const char*file, unsigned line)
    {
      std::clog<<var_name<<" = "<<t<<", "<<file<<", "<<line<<std::endl;
    }
    #define LOG(x) log(x, #x, __FILE__, __LINE__)
    

    Wenn es denn wirklich ohne inline Funktion gehen soll dann wenigstens in ((foo,(void)0)) packen und (x) bitte. Beispiel:

    #define LOG(x) ((std::clog << (x) << ", " << __FILE__ << ", " << __LINE__ << std::endl, (void)0))
    

    Ersters würde ich bevorzugen, weil es bessere Compilerfehlermeldungen ausspuckt.

    Warum du (void)0 verwendest ist mir zwar unklar. Letztendlich sollte mein Code ja auch nur zur Veranschaulichung dienen und nicht zur Eigenverwendung.
    Leider redest du an meinem Beitrag trotzdem vorbei. Es geht nicht darum, wie du das Makro LOG definierst. Von mir aus kannst du dort so viele inline Funktionen aufrufen und Klammern setzen wie du willst. Es geht einfach darum, dass du überhaupt ein Makro brauchst. Ansonsten bringt dir __FILE__ und __LINE__ herzlich wenig.



  • groovemaster schrieb:

    Ben04 schrieb:

    Na dann bin ich mal gespannt wie du hierzu eine Alternaitve basteln willst die genau so flexibel ist.

    Schon mal was von Templates gehört? 😉

    Ja und mit denen geht das nicht.

    groovemaster schrieb:

    Ausserdem gibts noch ein anderes Problem. Der Präprozessor kennt keine Namensräume. Was ist, wenn du Fremdcode nutzt und jemand hatte dort die gleiche Idee wie du? Das Problem besteht zwar grundsätzlich, ist aber nur ein weiterer Punkt, warum man Makros nur dann nutzen sollte, wenns wirklich nicht anders machbar ist.

    Das hat jetzt nichts mit dem Thema zu tun und ist nur ein Ausweichen auf eine Schwachstelle des Macrosystems allerdings stellt es in keiner Weise eine Lösung des Problems dar.

    groovemaster schrieb:

    Du solltest dir deshalb solche Spielereien abgewöhnen, ansonsten landest du irgendwann bei BOOST_FUNCTION_..., wo Code Lesen zur Tortur wird. In den seltensten Fällen ist sowas wirklich rentabel. Programmier deine Member-Funktionen schön nach dem WYSIWYG Prinzip 🙂 , dann weiss man sofort, was los ist (oder du sorgst dafür, dass man deine Header nicht findet und deine Doku immer aktuell ist 😃 ).

    Den Code 50 mal zu dublizieren und bei der kleinsten Änderung noch mal ganz durch zu gehen ist natürlich eindeutig besser und weniger fehleranfällig als ein Macro zu benutzen das man im Fall einer Namenskollision mit einem einfachen Suchen und Ersetzen umbenannt werden kann.

    groovemaster schrieb:

    Wenn deine Member-Funktionen von irgendwo anders ihre Funktionalität erhalten, dann nutze entsprechende Design Patterns, wie zB Policies.

    Könntest du ein auf diese Situation angewantes Beispiel bringen?

    groovemaster schrieb:

    Warum du (void)0 verwendest ist mir zwar unklar.

    Somit verhaält sich der Code wie eine Funktion die void zurück gibt mit dem kleinen Unterschied, dass man keinen Funktionszeiger kriegen kann. Ansonsten sind solche Macros eigentlich in der Verwendung syntaktisch mit Funktionen gleichsetzbar.

    groovemaster schrieb:

    Leider redest du an meinem Beitrag trotzdem vorbei. Es geht nicht darum, wie du das Makro LOG definierst. Von mir aus kannst du dort so viele inline Funktionen aufrufen und Klammern setzen wie du willst. Es geht einfach darum, dass du überhaupt ein Makro brauchst. Ansonsten bringt dir __FILE__ und __LINE__ herzlich wenig.

    Ich dachte es ging darum, dass Macros gefährlich sind. Durch das Umleiten auf eine reale Funktion kann man nun einmal die meisten Probleme lösen.



  • Ben04 schrieb:

    Ja und mit denen geht das nicht.

    Allein mit Templates natürlich nicht, da gehört schon noch mehr dazu. Das überlass ich dir aber einfach mal als Aufgabe. 😉

    Ben04 schrieb:

    Das hat jetzt nichts mit dem Thema zu tun und ist nur ein Ausweichen auf eine Schwachstelle des Macrosystems allerdings stellt es in keiner Weise eine Lösung des Problems dar.

    Ehrlich gesagt, sehe ich kein Problem, was man auf diese Art und Weise lösen müsste.

    Ben04 schrieb:

    Den Code 50 mal zu dublizieren und bei der kleinsten Änderung noch mal ganz durch zu gehen ist natürlich eindeutig besser und weniger fehleranfällig als ein Macro zu benutzen das man im Fall einer Namenskollision mit einem einfachen Suchen und Ersetzen umbenannt werden kann.

    Bezeichner kannst du mit Suchen und Ersetzen umbenennen, Änderungen damit aber nicht durchführen...hmm, interessant. 🙄

    Ben04 schrieb:

    Könntest du ein auf diese Situation angewantes Beispiel bringen?

    Sicher.

    struct Policy
    {
    	template <class T>
    	static T* clone(const T& source)
    	{
    		return bla;
    	}
    };
    
    class Base
    {
    public:
    	virtual Base* clone() const = 0;
    };
    
    class Derived
    	: public Base
    {
    public:
    	Derived(int);
    	Base* clone() const
    	{
    		return Policy::clone(*this);
    	}
    	virtual ~Derived();
    };
    
    class Derived2
    	: public Derived
    {
    public:
    	Derived2()
    		: Derived(5)
    	{
    	}
    	Base* clone() const
    	{
    		return Policy::clone(*this);
    	}
    };
    

    Ist zwar eine ziemlich statische Verwendung von Policies, um Code Verdopplung zu vermeiden aber vollkommen ausreichend.

    Ben04 schrieb:

    Somit verhaält sich der Code wie eine Funktion die void zurück gibt mit

    Und wofür soll das gut sein?

    Ben04 schrieb:

    Ich dachte es ging darum, dass Macros gefährlich sind.

    Nein, es ging darum, sinnvolle Anwendungen von Makros zu zeigen, die mit C++ Sprachmitteln so nicht machbar sind.


Anmelden zum Antworten