enum vs. #define



  • Nabend,

    Ich lese gerade mein C++ Buch und bin bei enum. Aber ich verstehe einfach nicht, worin der Unterschied zwischen normalen #define und enum besteht. Was ist so Vorteilhaft an enum??

    Es kommt doch auf's selbe hinaus, ob ich nun

    enum { elem1 = 1 } var1;
    

    oder

    #define var1 1;
    

    kommt doch eig. auf's selbe hinaus? Okay, bei enum kann man den Inhalt später noch ändern, aber da könnte ich auch genauso gut eine normale Variable deklarieren und einen Wert zuweisen, oder?

    Danke



  • Ein Vorteil:

    #define STATE_OFF 0
    #define STATE_ON 1
    
    void myfunc (int state);
    
    myfunc(2); // oops
    
    // besser:
    
    enum State { STATE_OFF, STATE_ON };
    
    void myfunc (State state);
    
    myfunc(2); // not allwed
    

    Scope-Vorteile, besseres Handling, also ich sehe eigentlich umgekehrt gar keinen einzigen Grund ein #define zu verwenden.

    MfG SideWinder



  • Ein enum hat einen Datentyp und einen definierten Wertebereich. Da kann der Compiler gewisse Prüfungen machen und z.B. Warnungen erzeugen wenn ein ungültiger Wert auftaucht.

    Ein Define ist dagegen nicht einmal ein Element der Sprache C++, sondern eher ein Überbleibsel aus frühen Tagen von C. es ist lediglich eine simple Text-Ersetzungs die von dem Präprozessor ausgeführt wird bevor der Compiler läuft. Entsprechend hat ein define z.B. keinen Typ. Abgesehen von den üblichen Ausnahemn ist #define in C++ ein Zeichen für schelchtes OOP-Design.



  • Was ist an:

    #define LOG(msg) Logger::log((msg), __FILE__, __LINE__);
    

    schlechtes OOP-Design?

    MfG SideWinder



  • Ist doch gerade ein schönes Beispiel warum Macros schlecht sind. Wenn Du irgendwo im Code LOG("Hallo") stehen hast, woher weist Du dann welcher Code sich dahinter verbirgt? Richtig, Du mußt es nachschauen. Du mußt Dir aber auch bewußt machen unter welchen Umständen welcher Code da stehen wird und spätestens wenn es mehrere Bedingungen gibt könntest Du als Mensch da irgendwann auch einfach falsch liegen.

    Im OOP-Umfeld würde ich solche Lösungen wie Log4J jederzeit über so einen Macro-wisch-wasch vorziehen.

    Lustig wirds wenn Du so ein Macro wie:

    #undef TRUE
    #define TRUE 0

    hast... Lach nicht, das ist mir schon öfter begenet als mir lieb ist und die verantworlichen "Programmierer" hatten auch immer angeblich gute Gründe das so zu machen. (Nicht das ich diese mit ihnen geteilt hätte...)

    Im Klartext kann sowas aber bedeuten, das TRUE in einem Sourcefile einen anderen Wert hat als in enem anderen, mit einem enum ist sowas nicht möglich.



  • Squall3 schrieb:

    Nabend,

    Ich lese gerade mein C++ Buch und bin bei enum. Aber ich verstehe einfach nicht, worin der Unterschied zwischen normalen #define und enum besteht. Was ist so Vorteilhaft an enum??

    #define wird vom Preprozesser behandelt. Das ist ein Programm was vor dem eigentlichen kompilieren aufgerufen wird und das Programm ist ziemlich dump. Es ersetzt einfach blind alles was auf das mit #define definierte Makro zutrifft. Dabei kümmert es sich nicht um Scopes, Namespaces oder was auch immer. Der Compiler bekommt am Ende den Quellcode mit der Ersetzung. Er sieht also auch nicht was du wolltest, sondern nur was passiert ist. Das kann zB bei Fehlermeldungen sehr verwirrend sein (weil der Compiler eben Fehlermeldungen zu einem Quellcode ausgibt, den du so gar nicht siehst)

    enum dagegen ist ein Bestandteil der Programmiersprache und wird vom Compiler behandelt, mit allen Berücksichtigungen, die man normalerweise erwartet (Scopes, Namespaces etc.)

    Daher sollte man #define versuchen zu vermeiden, wo man es vermeiden kann

    Okay, bei enum kann man den Inhalt später noch ändern, aber da könnte ich auch genauso gut eine normale Variable deklarieren und einen Wert zuweisen, oder?

    Du hast ja auch nur eine Variable deklariert

    im Grunde hast du folgendes gemacht
    [cpp]enum { elem1 = 1 };

    int var1 = elem1;[cpp]



  • hja schrieb:

    ..., woher weist Du dann welcher Code sich dahinter verbirgt? Richtig, Du mußt es nachschauen.

    IDE erkennt und zeigt. Leider ist das in C++ so schwer möglich, ein weiterer Grund warum sich andere Sprachen derzeit auf dem Vormarsch befinden 🤡

    hja schrieb:

    Im OOP-Umfeld würde ich solche Lösungen wie Log4J jederzeit über so einen Macro-wisch-wasch vorziehen.

    Da bringst du aber ein 1A-Beispiel für C++, Ausschnitt aus der Log4cxx-Doku:

    //
    //    Use these instead of calling Logger methods directly.
    //
    #define LOG4CXX_DEBUG(logger, msg) ...
    #define LOG4CXX_INFO(logger, msg) ...
    #define LOG4CXX_WARN(logger, msg) ...
    #define LOG4CXX_ERROR(logger, msg) ...
    #define LOG4CXX_FATAL(logger, msg) ...
    

    hja schrieb:

    Lustig wirds wenn Du so ein Macro wie:

    #undef TRUE
    #define TRUE 0

    Wo habe ich behauptet, dass Makros eine gute Idee sind um Konstanten einzuführen. Die Verwendung von Makros deutet nur ganz einfach nicht auf ein schlechtes OOP-Design hin.

    MfG SideWinder

    Edits: Der Einsatz von Code-Tags wird immer schwieriger je später es ist 🤡



  • [quote="SideWinder]Wo habe ich behauptet, dass Makros eine gute Idee sind um Konstanten einzuführen. Die Verwendung von Makros deutet nur ganz einfach nicht auf ein schlechtes OOP-Design hin.
    [/quote]

    eine Sache fällt mir zu dem Thema noch ein. Im Grunde untergräbt ein Macro einen Grundgedanken von OOP, die Kapselung von Sourcecode in einer Klasse, weil das Macro sich ähnlich einer globalen Variablen verhält und "von außen" Sourcecode in die Klasse bringt der obendrein Situativ anders sein kann. Dabei untergräbt das Makro Sicherheitsmaßnahmen wie privat, protected usw.

    Jeder Mechanismus der die Datenkapselung verwaessert bzw aushebelt ist imho schelchtes OOP-Design.

    PS: sorry, konnte nicht widerstehen 😉



  • Hm da war doch auch noch was mit der Zeilenangabe bei Compilerfehlern.

    #define abc 1
    
    void f(std::string);
    
    int main()
    {
       f(abc);
    }
    

    Fehlermeldung:
    Zeile 7: 1 ist kein string

    "Wo steht da 1?"

    ________________

    enum( abc = 1}
    
    void f(std::string);
    
    int main()
    {
       f(abc);
    }
    

    Fehlermeldung: Zeile 7 enum abc ist kein string.

    Dürfte gerade bei komplexeren sachen leichter zu erkennen sein.



  • benutz mal " "

    und enums sind zahlen und keine strings

    { red, blue, yellow, grey = 100, black}

    red = 0
    blue = 1
    ...
    grey = 100
    black = 101

    bei dem define kommt eine textersetzung ....
    also schreibst du rein abc(l), ist das ein string -.- ?



  • bei dem define kommt eine textersetzung ....
    also schreibst du rein abc(l), ist das ein string -.- ?

    Nun ja, im Prinzip schon, doch std::string weiß das ohne weiteres einfach nicht 🙂



  • im prinzip eben nicht -.- das ist ein parameter



  • Mit define definierte Konstanten sind völlig Typlos, könnten also als fast alles geplant sein, wieso kann diese 1 dann nicht als Konstante vom Typ char geplant sein?
    Schließlich handelt es sich bei charvariablen ebenso wie bei ints um einen integralen Datentyp, weswegen man strings natürlich auch aus integrale Zahlen zusammensetzen kann.
    Gibt man z.B. char blub[] = { 72, 97, 108, 108, 111, 0 }; auf dem Bildschirm aus, erscheint dort "Hallo", ersetzt man hier char durch int erschiene auf dem Bildschirm "72971081081110"(je nachdem welchen Hinweis man dem Compiler zur Interpretation angibt).
    Und diese 1 repräsentiert bei der (bei mir) standardmäßig gewählten Schriftart in der Konsole ein kleines Smilie :).

    Nur das "Problem" ist, die meisten Compiler erdreisten bzw erbarmen sich (je nachdem wie man das denn sehen möchte) eine integrale Zeichenliterale ohne typanzeigenden Zusatz so zu interpretieren, als SEI(!) diese integrale Zeichenliterale vom Typ int.
    Sicherlich wird dies wohl deswegen so geregelt, weil dies wohl häufig so gedacht ist.

    Doch ändert dies nichts an der Tatsache, dass eine 1 bzw irgendwelche anderen Zahlen als integrale Zeichenliterle, im Prinzip durchaus einen String bilden können.
    Die Angabe von (char)72 oder 'H' dürfte wohl äquivalent sein oO.



  • @hja

    SideWinder schrieb:

    Was ist an:

    #define LOG(msg) Logger::log((msg), __FILE__, __LINE__);
    

    schlechtes OOP-Design?
    MfG SideWinder

    gar nichts.
    auch an 5+5 ist kein schlechtes OOP-design zu erkennen.
    (/me fühlt ein "java.lang.Math.oprator+(5,5)//nur so ist es OO" anklopfen)
    vielleicht mag man herumspielen mit sachen wie

    #define LOG(msg) Logger::log(__FILE__<<__LINE__)<<msg;
    

    , um auch problemlos

    LOG("cant open file "<<fileName);
    

    loggen zu können.
    aber sooft man es dreht und wendet, beim logging kommen stets makros heraus.



  • volkard schrieb:

    aber sooft man es dreht und wendet, beim logging kommen stets makros heraus.

    Auch nur, weil es in C++ keinen Stacktrace gibt, um die Position im Code zu ermitteln



  • #define abc 1
    
    void f(std::string);
    
    int main()
    {
       f(abc);
    }
    

    😡

    du übergibst als string also übergibst du einen parameter

    ersetz sieht es dann so aus:

    void f(std::string);
    
    int main()
    {
       f(1);
    }
    

    verstehste nun ?



  • Wie wäre es wenn du einfach liest was ich schreibe?

    Nur weil der Compiler da mecker, heißt das doch noch lange nicht, dass eine 1 kein String sein kann.
    Sowohl int als auch char sind integrale Datentypen, beide speichern ganze Zahlen, hinter Buchstaben verstecken sich nunmal ganze Zahlen.

    Die integrale Zeichenliterale 1 (wie zu sehen ist ohne Datentypanzeigenden Zusatz) hat so lange keinen Typ, bis sie in einen bestimmten Typ gecastet wird und sei dies nur implizit.

    #define abc 1 // <-- Ohne Datentyp 
    void f(std::string);
    int main(int argc, char* argv[])
    {
       f(abc);   // <-- Daraus wird "f(1);" Die Datentyplose integrale
                 // Literale(welche sowohl als int als auch als (z.B.) char oder
                 // String interpretiert werden kann) wird hier in eine int-
                 // konstante gecastet.
                 // Bei den meisten Compilern ist es nunmal "voreingestellt",
                 // dass integrale konstanten ohne typanzeigenden Zusatz
                 // in int-konstanten GECASTET (<-- Vor dem Cast natürlich kein
                 // eindeutiger int!) werden,
                 // manche Leute nennen das implizitcast oO.
                 // Soll heißen, überall wo während der Compilierung eine 
                 // integrale Literale steht, steht intern sowas wie (int)Literale.
                 // Nunja, zumindest mindest lässt sich auf diese Weise 
                 // verdeutlichen worauf ich hinaus will.
                 // Demnach lässt sich "f(1);" so LESEN, als WÜRDE
                 // dort "f( (int)1 );" stehen
    
       // compilier das Programm mal mit folgenden Zeilen
       f(char(abc)); // char() zur Aushebelung des Implizit Casts
    
       char zeichen = abc;
       int zahl = abc;
       std::cout << zeichen << (char)zahl << std::endl;
       std::cout << (int)zeichen << zahl << std::endl;
       // Sollte es sich bei dir bei der 1 um ein nichtdruckbares Zeichen handeln,
       // versuch es einfach mal mit 72 oder sowas, ist garantiert was druckbares
       // drinne.
    
    return 0;
    }
    

    Soll heißen, eine 1, so ganz nackt im Quellocde, ist erst dann ein Int, wenn sie während der Compilierung einen impliziten Cast erfährt, wohlgemerkt - CAST, von "irgendwas" in int oO



  • Um dagegen zu halten schrieb:

    volkard schrieb:

    aber sooft man es dreht und wendet, beim logging kommen stets makros heraus.

    Auch nur, weil es in C++ keinen Stacktrace gibt, um die Position im Code zu ermitteln

    vor allem, weil man das loggen einigermaßen zentrall ausschalten können mag, ohne daß die parameter ausgewertet werden und funktionen aufgerufen werden.



  • EDIT: Is egal..



  • Kahino schrieb:

    Nur weil der Compiler da mecker, heißt das doch noch lange nicht, dass eine 1 kein String sein kann.

    Sorry das ich das sage: aber was Du meinst, ist sowas von unerheblich, das glaubst du garnicht. Weil nur das Zählt, was der ISO-C++-Standard und somit jeder weitestgehend konforme Compiler dazu sagt.

    War jetzt hart für dich, aber das mußte einfach sein. 😉



  • Kahino schrieb:

    Nur das "Problem" ist, die meisten Compiler erdreisten bzw erbarmen sich (je nachdem wie man das denn sehen möchte) eine integrale Zeichenliterale ohne typanzeigenden Zusatz so zu interpretieren, als SEI(!) diese integrale Zeichenliterale vom Typ int.

    Naja. Sie erdreisten sich, dem C++-Standard zu folgen, der das so fordert. In C sind BTW sogar Zeichenliterale wie 'H' vom Typ int.


Anmelden zum Antworten