Aufzählungen (enum) sind eine beliebte Art, sprechende Integer-Konstanten (Aufzähler) zu definieren. Leider besitzen sie in klassischem C++ einige Nachteile.
Die Nachteile von Aufzählungen in klassischem C++
Zur kurzen Auffrischung. Die 3 Nachteile von Aufzählungen.
- Sie konvertieren implizit zu int.
- Sie führen ihren Bezeichner in den umgebenden Bereich ein.
- Der zugrunde liegende Typ kann nicht angegeben werden.
Zuerst zum Punkt 3. Da der zugrunde liegende Typ nicht angegeben werden kann, können Aufzählungen nicht vorwärts deklariert werden Für den zugrunde liegenden Typ der Aufzählung gilt in klassischem C++ die Zusicherung, dass er groß genug sein muss, um alle Aufzähler darstellen zu können.
Punkt 1 und 2 besitzen größeres Überraschungspotential.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// enumClassic.cpp #include <iostream> int main(){ std::cout << std::endl; enum Colour{red= 0,green= 2,blue}; std::cout << "red: " << red << std::endl; std::cout << "green: " << green << std::endl; std::cout << "blue: " << blue << std::endl; int red2= red; std::cout << "red2: " << red2 << std::endl; // int red= 5; ERROR } |
Einerseits sind die Aufzähler red, green und blue im umgebenden Bereich bekannt. Damit ist die Definition der Variable red in Zeile 19 nicht möglich. Andererseits lässt sich red implizit nach int konvertieren.
Wird kein Name für eine Aufzählung enum{red, green, blue}; verwendet, werden die Aufzähler zu mindestens in den umgebenden Bereich eingeführt.
Mit diesen Überraschungen räumt C++11 auf.
Streng typisierte Aufzählungstypen
Für die streng typisierten Aufzählungstypen gelten deutlich strengere Regeln.
- Die Aufzähler lassen sich nur im Gültigkeitsbereich der Aufzählung ansprechen.
- Die Aufzähler konvertieren nicht implizit zu int.
- Die Aufzählungstypen führen ihren Aufzähler nicht in den umgebenden Bereich ein.
- Ihr zugrunde liegender Typ ist per Default int, so dass Aufzählungstypen automatisch vorwärts deklariert werden können.
Der syntaktische Unterschied der klassischen Aufzählungstypen zu den streng typisierten Aufzählungstypen ist minimal. Die streng typisierten Aufzählungstypen erhalten bei ihrer Deklaration zusätzlich das Schlüsselwort class oder struct.
Soll ein Aufzähler eines streng typisierten Aufzählungstyp als Datentyp int verwendet werden, ist eine Konvertierung mit static_cast notwendig.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
// enumCast.cpp #include <iostream> enum OldEnum{ one= 1, ten=10, hundred=100, thousand= 1000 }; enum struct NewEnum{ one= 1, ten=10, hundred=100, thousand= 1000 }; int main(){ std::cout << std::endl; std::cout << "C++11= " << 2*thousand + 0*hundred + 1*ten + 1*one << std::endl; std::cout << "C++11= " << 2*static_cast<int>(NewEnum::thousand) + 0*static_cast<int>(NewEnum::hundred) + 1*static_cast<int>(NewEnum::ten) + 1*static_cast<int>(NewEnum::one) << std::endl; } |
Um die Aufzähler zu verrechnen oder auszugeben, müssen sie in einen integralen Typ konvertiert werden. Weder ist die Addition noch die Ausgabe für Aufzähler von streng typisierten Aufzählungstypen definiert.
In diesem Artikel spreche ich immer von klassischen versus streng typisierten Aufzählungstypen. Gerne werden diese zwei Arten von Aufzählungen auch Aufzählungen mit und ohne Geltungsbereich genannt.
Explizite Angabe des Typs
Eine Erweiterung der Aufzählungstypen in C++11 habe ich verschwiegen. In C++11 lässt sich explizit der zugrunde liegende Typ der Aufzählung spezifizieren. Per Default ist es der Typ int.
Es geht aber auch anders. Als Typ kann ein integrale Typ wie bool, char, short int , int , long int oder auch long long int verwendet werden. Die ganzen Details zu integralen Typen lassen sich schön auf msdn.microsoft.com nachlesen. Wie zur Compilezeit verifiziert werden kann, ob ein Typ integral ist, zeigt der Artikel Typeigenschaften abfragen dieses Blogs.
Der Geltungsbereich und die explizite Typangabe einer Aufzählung sind unabhängig voneinander einsetzbar. Abhängig vom zugrunde liegenden Typ sind die Aufzählungstypen verschieden groß.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
// enumType.cpp #include <iostream> #include <climits> enum struct Colour0: bool{ red, // 0 blue // 1 }; enum Colour1{ red= -5, blue, // -4 green // -3 }; enum struct Colour2: char{ red= 100, blue, // 101 green // 102 }; enum class Colour3: long long int{ //red= std::numeric_limits<long long int>::min(); red= LLONG_MIN, blue, // std::numeric_limits<long long int>::min() + 1 green // std::numeric_limits<long long int>::min() + 2 }; int main(){ std::cout << std::endl; std::cout << "sizeof(Colour0)= " << sizeof(Colour0) << std::endl; std::cout << "sizeof(Colour1)= " << sizeof(Colour1) << std::endl; std::cout << "sizeof(Colour2)= " << sizeof(Colour2) << std::endl; std::cout << "sizeof(Colour3)= " << sizeof(Colour3) << std::endl; std::cout << std::endl; std::cout << "Colour0::red: " << static_cast<bool>(Colour0::red) << std::endl; std::cout << "red: " << red << std::endl; std::cout << "Colour2::red: " << static_cast<char>(Colour2::red) << std::endl; std::cout << "Colour3::red: " << static_cast<long long int>(Colour3::red) << std::endl; } |
Leider kann mein in Microsoft Visual Studio 12.0 enthaltene C++-Compiler cl.exe den Ausdruck std::numeric_limits<long long int>::min() in Zeile 24 nicht zur Compilezeit evaluieren. Laut C++11-Standard ist std::numeric_limits<long long int>::min() ein konstanter Ausdruck (constexpr) und sollte damit zum Initialisieren eines Aufzähler verwendet werden können. Da bleibt mir nichts anderes übrig, als das Makro LLONG_MIN in Zeile 25 anzufassen. Dies ist wie std::numeric_limits in der Headerdatei <climits> definiert.
Zum Abschluss noch die Ausgabe.
Wie geht's weiter?
In der Programmierung von embedded System geht es typischerweise um Systeme von Systemen. Oder anders ausgedrückt. Viele autonome Systeme interagieren miteinander um das Gesamtsystem zu bilden. Tausche ich nun den Begriff autonomes System durch Objekt aus, so sind wir in der Domäne der objektorientierten Programmierung. Für mich ist die objektorientierte Denke eine Abstraktion, die einen großen Mehrwert für das Verständnis von embedded Systemen besitzt. Da ist es umso besser, dass C++11 mit den neuen Schlüsselwörtern override und final hilft, die Objekthierarchien besser unter Kontrolle zu bekommen.
Im nächsten Artikel werde ich mir die neuen Schlüsselwörter override und final genauer anschauen.
Go to Leanpub/cpplibrary "What every professional C++ programmer should know about the C++ standard library". Hole dir dein E-Book. Unterstütze meinen Blog.
Weiterlesen...