CodeGym /Kurse /C++ SELF /Abgeschlossene Wertemenge: enum und enum class

Abgeschlossene Wertemenge: enum und enum class

C++ SELF
Level 19, Lektion 1
Verfügbar

1. Einführung

Der gestrige Datensatz war sehr bequem:

struct Task {
    int id;							// Aufgaben‑id
    std::string title;	// Aufgabenname
    bool done = false;  // Status: erledigt/nicht erledigt
};

Das Problem beginnt an dem Tag, an dem Sie einen dritten Status hinzufügen möchten: „in Bearbeitung“.

Und hier versagt bool, weil er nur „ja/nein“ kann, während wir „drei verschiedene Bedeutungen“ wollen.

Der häufigste „Quick Fix“ von Anfängern ist, ein zweites Flag hinzuzufügen:

struct Task {
    int id;
    std::string title;
    bool done = false;
    bool inProgress = false; // können beide true sein? oder beide false?
};

Der Code kompiliert. Die Logik — nicht mehr. Jetzt sind Zustände möglich:

  • done = true
    und
    inProgress = true
    (Aufgabe gleichzeitig erledigt und in Bearbeitung… Quantenmechanik?),
  • done = false
    und
    inProgress = false
    (scheinbar „todo“, aber das ist eine Vereinbarung „zwischen den Zeilen“).

Je mehr Status — desto mehr Flags und desto mehr „illegale Kombinationen“.

„Wir machen int status“ — magische Zahlen betreten die Bühne

Nächste Idee: „Ich kodiere es einfach“.

int status = 0; // 0=todo, 1=in_progress, 2=done (im Kopf)
status = 42;    // der Compiler hat nichts dagegen, aber die Logik weint

Der Nachteil liegt nicht im Typ int. Der Nachteil ist, dass der Typ die Bedeutung nicht ausdrückt. Wenn Sie in einer Woche if (status == 2) sehen, ist das kein Code — das ist ein Rätsel.

„Wir machen std::string status“ — Tippfehler werden zum neuen Status

Wir wechselten zu Strings, aber es wurde nicht besser:

std::string status = "done";
status = "inprogres"; // Tippfehler -> „neuer Status“, über den sich niemand geeinigt hat

Strings führen oft zu einem Variantenzoo: "Done", "done", "DONE", "in progress", "in_progress"…

Was wir wirklich brauchen

Wir brauchen einen Typ, der sagt: der Wert kann nur einer von vorher bekannten Varianten sein. Das sind Aufzählungen: enum und enum class. enum ist die ältere Variante und wird selten noch verwendet. Daher schauen wir uns direkt das moderne enum class an.

Kleine „Ergebnis‑Tabelle“:

Wie den Status speichern Beispiel Problem
bool
done = true/false
nur 2 Zustände, danach fangen Hacks an
int
status = 2
magische Zahlen, leicht „versehentlich 42“
std::string
"in_progress"
Tippfehler erzeugen neue „Werte“
enum class
Status::InProgress
type‑safe und selbstdokumentierend

2. enum class: Basissyntax und Arbeit mit Werten

Deklaration

Mit enum class kann man einen neuen Typ erstellen, indem man alle möglichen Werte aufzählt:

enum class Status {
    Todo,
    InProgress,
    Done
}; // ja, das Semikolon ist nötig

Hier:

  • Status — Typname
  • Todo, InProgress, Done — Aufzählungswerte (mögliche Werte)
  • ; nach } ist verpflichtend (wie bei struct)

Erstellen, zuweisen, vergleichen

Eine Aufzählung ist ein „normaler“ Typ: man kann sie in einer Variablen speichern, zuweisen und vergleichen.

enum class Status { Todo, InProgress, Done };

void Demo() {
    Status s = Status::Todo;        // erstellt
    s = Status::InProgress;         // zugewiesen

    if (s == Status::InProgress) {  // verglichen
        // etwas tun
    }
}

Gewohnheit, die man sich merken sollte: bei enum class schreibt man die Werte als Status::Todo, nicht nur Todo. Das ist kein unnötiger Lärm, sondern Namensschutz gegen Konflikte.

Wichtiger Hinweis: enum class wird nicht automatisch zu int oder bool

Der alte Typ enum konnte sich automatisch in int verwandeln und sogar als „wahr/false“ benutzt werden.

enum Status { Todo, InProgress, Done };

void Bad() {
    Status s = Done;
    int x = s;
    if (s) { }
}

Sehr bequem und sehr… problematisch. Im neuen enum class wurde das abgeschafft. Der Compiler lässt nicht mehr zu, dass „Status“ versehentlich als Zahl oder Bool genutzt wird.

3. Wir fügen enum class in unser Modell Task ein

Wir schreiben Task um: statt bool done verwenden wir einen echten Status.

#include <string>

enum class Status { Todo, InProgress, Done };

struct Task {
    int id;
    std::string title;
    Status status = Status::Todo; // Standardstatus
};

Jetzt werden Aufgaben deutlich „sprechender“ erzeugt:

Task a{1, "Buy milk"};                     // status = Todo (Standard)
Task b{2, "Pay rent", Status::InProgress}; // explizit gesetzt

Und eine nette Kleinigkeit: es ist unmöglich, aus Versehen etwas Unpassendes zuzuweisen.

Task t{1, "Fix bug"};
// t.status = 2; // Kompilierungsfehler: genau das, was wir wollen

4. Status und Prioritäten: noch ein enum class

Da wir gelernt haben, wie man „abgeschlossene Wertemengen“ ausdrückt, fügen wir eine weitere Menge hinzu: Priorität.

enum class Priority {
    Low,
    Normal,
    High
};

Und erweitern das Modell Task. Gute Nachricht: wir können das Feld so hinzufügen, dass alter Code kaum bricht — einfach einen Standardwert vergeben.

#include <string>

enum class Status   { Todo, InProgress, Done };
enum class Priority { Low, Normal, High };

struct Task {
    int id;
    std::string title;
    Status status = Status::Todo;
    Priority priority = Priority::Normal;
};

Nun können wir schreiben:

Task a{1, "Buy milk"}; // Todo + Normal
Task b{2, "Pay rent", Status::InProgress}; // InProgress + Normal
Task c{3, "Fix prod", Status::Todo, Priority::High}; // Todo + High

Und hier wird Type‑Safety fast „greifbar“:

Task t{1, "Write report"};

t.status = Priority::High; // Fehler: unterschiedliche Typen!
t.priority = Status::Done; // ebenfalls Fehler

Wären beide Felder int, wären solche Fehler real und sehr unangenehm.

5. Was ist besser: enum vs enum class

In C++ gibt es zwei Arten von Aufzählungen:

  • enum — unscoped (alter Stil)
  • enum class — scoped (moderner Stil, meist bevorzugt)

Problem des normalen enum: Namen „laufen aus“

enum Color { Red, Green, Blue };
enum TrafficLight { Red, Yellow, Green }; // Namenskonflikt Red/Green

In großen Projekten wird das zu konstanten „Warum ist Red schon belegt?“-Konflikten.

Problem des normalen enum: zu leicht in int zu werden

enum Color { Red, Green, Blue };

void Surprise() {
    int x = Red;      // kompiliert
    if (Green == 1) { // kompiliert (fragwürdig)
        // ...
    }
}

Das heißt, enum kann in Zahlen „auslaufen“ und Sie sind wieder bei magischen Werten.

Was enum class bietet

Vorteile von enum class:

  • hält Namen innerhalb des Typs (Beispiel: Status::Done)
  • erlaubt keine implizite Konversion in Zahl/Bool

Kleine Merkhilfe:

Eigenschaft
enum
enum class
Namen der Werte im aktuellen Sichtbereich innerhalb des Typs (
Status::Done
)
Implizite Konversion zu
int
oft ja nein
Weniger Namenskonflikte nein ja
„Standard“ in modernem C++ eher nein ja

Praktische Regel des Kurses: wenn Sie unsicher sind — nehmen Sie enum class.

6. Numerische Werte der Aufzählungswerte

„Standard“-Werte

Wenn Sie die Zahlen nicht explizit angeben, laufen die Werte normalerweise als 0, 1, 2 … (in Reihenfolge).

enum class Status { Todo, InProgress, Done };
// normalerweise Todo=0, InProgress=1, Done=2

Wichtig: das ist eine Darstellungs‑Detail, nicht die Bedeutung. Die Bedeutung schreibt man so:

if (t.status == Status::Done) {
    // Aufgabe ist fertig
}

und nicht so:

if (code == 2) { ... } // das bringt uns zurück zu magischen Zahlen

Explizite Codes: wann das wirklich nötig ist

Manchmal benötigt man stabile Zahlen (z. B. Dateiformat/Protokoll/Integration mit fremder API). Dann setzt man Werte explizit:

enum class Status {
    Todo = 10,
    InProgress = 20,
    Done = 30
};

Wenn Sie einem Element einen Wert geben, zählt das nächste ohne Wert weiter:

enum class Status {
    Todo = 10,
    InProgress, // 11
    Done        // 12
};

Das ist praktisch, verlangt aber Disziplin: wenn Sie Zahlen einführen — dokumentieren Sie, warum.

7. Underlying type: welcher Typ wird tatsächlich im enum gespeichert

Die Aufzählung wird im Speicher als ganze Zahl abgelegt. Der Typ dieser Zahl heißt underlying type (Basis‑Typ des enum). Meistens ist das etwas wie int, aber manchmal möchte man den Typ explizit angeben.

Häufigste Gründe:

  • Kompatibilität mit externem Format (z. B. „im File wird der Status als 1 Byte gespeichert“)
  • Speicherersparnis in großen Arrays/Tabellen
  • klaren Bereich begrenzen wollen

Syntax: : std::uint8_t

So ersetzt man das interne int durch std::uint8_t:

#include <cstdint>

enum class Status : std::uint8_t {
    Todo,
    InProgress,
    Done
};

Hier gibt es zwei praktische Hinweise.

Erstens: alle Werte müssen in den gewählten Typ passen. Zweitens: auch wenn Status 1 Byte groß ist, kann sizeof(Task) wegen Alignment (Padding) nicht kleiner werden. Das ist kein Bug, das ist normale „Speichergeometrie“.

Wenn Sie experimentieren möchten (und zugleich sehen wollen, dass der Compiler ein lebender Organismus ist, kein Taschenrechner), können Sie Größen ausgeben:

#include <iostream>

int main() {
    std::cout << sizeof(Status) << '\n';
    std::cout << sizeof(Task) << '\n';
}

Die Ergebnisse können zwischen Compilern/Plattformen variieren — das ist normal.

8. Wie erhält man den numerischen Wert eines enum

std::to_underlying — der korrekte „offizielle“ Weg

In C++23 gibt es die Funktion std::to_underlying, die den underlying‑Wert der Aufzählung zurückgibt.

#include <iostream>
#include <utility> // std::to_underlying

enum class Status { Todo, InProgress, Done }; // 0, 1, 2

int main() {
    Status s = Status::Done;
    std::cout << std::to_underlying(s) << '\n'; // 2
}

Das ist nützlich zum Debugging oder zum „Durchreichen“ in ein Protokoll/Datei (wenn das so vereinbart ist).

Falle: std::uint8_t in cout kann als Zeichen ausgegeben werden

Wenn der underlying type std::uint8_t ist, sieht man beim Stream‑Ausgabe eventuell ein „Zeichen“ statt einer Zahl. Denn uint8_t verhält sich oft wie unsigned char.

#include <cstdint>
#include <iostream>
#include <utility>

enum class Status : std::uint8_t { Todo, InProgress, Done };

int main() {
    auto code = std::to_underlying(Status::Done);
    std::cout << static_cast<int>(code) << '\n'; // als Zahl ausgeben
}

Regel: Wenn das underlying „byte“ ist, casten Sie zum Ausgeben auf int.

Wenn std::to_underlying nicht verfügbar ist: static_cast

Manchmal steckt die Umgebung in einem älteren Standard (oder C++23 ist nicht aktiviert). Dann hilft das explizite Casting:

enum class Status { Todo, InProgress, Done };

int code = static_cast<int>(Status::Done); //  2

Der Sinn ist derselbe: Sie sagen dem Compiler ausdrücklich „ja, ich will die Zahl“.

9. Gefährliche Zone: Konversion von einer Zahl in ein enum

Tatsächlich ist static_cast<Status>(42) eine potenzielle Zeitbombe. So ein Code kompiliert:

enum class Status { Todo, InProgress, Done };

Status s = static_cast<Status>(42); // formal ok, logisch — Müll

Sie haben gerade einen Status erzeugt, der im Aufzählungstyp kein Namen hat. Das bricht die Idee der „abgeschlossenen Wertemenge“.

enum class schützt vor Zufallsfehlern, aber wenn Sie „mit dem Vorschlaghammer“ durch static_cast die Tür aufstoßen, ist der Compiler nicht verpflichtet, Sie zu retten.

Feinheit bei Status s{};

Value‑Initialisierung ({}) ergibt ein underlying‑Null‑Wert.

enum class Status { Todo, InProgress, Done };

Status ok{}; // underlying=0 -> das ist Todo

Aber wenn Sie Codes nicht bei Null beginnen:

enum class Status { Todo = 10, InProgress = 20, Done = 30 };

Status bad{}; // underlying=0 -> das ist NICHT Todo, das ist ein „unnamed“ Wert

Praktischer Schluss: Wenn Sie einen Default‑Status wollen, geben Sie ihn explizit an. In unserem Modell machen wir das so:

Status status = Status::Todo;

Wenn Sie ein enum aus einer Zahl erhalten müssen — validieren Sie

Wir behandeln noch nicht std::optional und „korrekte Parser‑Ergebnisse“ (das kommt später), aber das Prinzip lässt sich einfach zeigen: prüfen Sie die Zahl vor der Zuweisung.

enum class Status { Todo, InProgress, Done };

bool TryParseStatus(int code, Status& out) {
    if (code == 0) { out = Status::Todo; return true; }
    if (code == 1) { out = Status::InProgress; return true; }
    if (code == 2) { out = Status::Done; return true; }
    return false; // unbekannter Code
}

Ja, hier tauchen wieder Zahlen auf — aber jetzt leben sie an einer Stelle und werden nur als Eingabeformat benutzt, nicht als Grundlage der Logik im ganzen Programm.

10. using enum: weniger Tippen für Status::...

C++20 hat using enum eingeführt, mit dem man die Enumerationswerte in den aktuellen Sichtbereich „holen“ kann.

enum class Status { Todo, InProgress, Done };

void Demo() {
    using enum Status;   // Todo/InProgress/Done sind ohne Status:: sichtbar
    Status s = Todo;
    s = InProgress;
}

Das ist praktisch, aber gute Praxis lautet: nutzen Sie using enum lokal, innerhalb einer Funktion. Wenn Sie es global machen, erzeugen Sie leicht Konflikte und fragen sich „was ist Todo und woher kommt es?“.

11. Typische Fehler beim Arbeiten mit enum / enum class

Fehler Nr. 1: Semikolon ; nach der Aufzählung vergessen.
Symptome sehen oft aus wie „expected ‘;’ …“ oder seltsame Fehler in den folgenden Zeilen. Heilung: enum class {...}; — immer mit Semikolon.

Fehler Nr. 2: schreiben Todo statt Status::Todo.
Bei enum class liegen die Werte im Typ. Das ist extra so, um Konflikte zu vermeiden und den Code eindeutig lesbar zu machen. Wenn Sie es kürzer wollen, nutzen Sie using enum Status; lokal in einer Funktion.

Fehler Nr. 3: versuchen, enum class als int oder bool zu verwenden.
enum class konvertiert sich nicht automatisch in Zahlen oder logische Werte. Das ist kein Sprachlauneflick — es schützt vor sinnlosen Prüfungen wie if (status) oder Zuweisungen wie status = 2;.

Fehler Nr. 4: Geschäftslogik auf Zahlen 0/1/2 aufbauen.
Wenn Sie irgendwo if (code == 2) sehen, hat das enum seine Aufgabe nicht erfüllt, und Sie sind wieder bei magischen Zahlen. Richtiger Code vergleicht mit Status::Done, nicht mit 2.

Fehler Nr. 5: static_cast<Status>(Zahl) ohne Prüfung.
So kann man einen „unnamed“ Status erhalten, der weder Todo noch InProgress noch Done ist. Danach verhält sich das Programm seltsam, weil Sie selbst den Typenschutz umgangen haben.

1
Aufgabe
C++ SELF, Level 19, Lektion 1
Gesperrt
Tagesbegrüßung
Tagesbegrüßung
1
Aufgabe
C++ SELF, Level 19, Lektion 1
Gesperrt
Ampelbefehl
Ampelbefehl
1
Aufgabe
C++ SELF, Level 19, Lektion 1
Gesperrt
Support-Ticket
Support-Ticket
1
Aufgabe
C++ SELF, Level 19, Lektion 1
Gesperrt
Player-Zustand
Player-Zustand
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION