Menu
CoddyTech

optional, variant, any

Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy. Lektion 89 von 104.

Modernes C++ bietet drei Vokabulartypen in den Headern <optional>, <variant> und <any>, die dir dabei helfen, mit Werten umzugehen, die fehlen könnten, einen von mehreren Typen enthalten oder überhaupt einen beliebigen Typ speichern.

std::optional<T> stellt einen Wert dar, der vorhanden sein kann oder auch nicht – perfekt für Funktionen, die möglicherweise fehlschlagen, ohne eine Ausnahme auszulösen:

#include <iostream>
#include <optional>
std::optional<int> findIndex(const std::string& str, char c) {
    for (size_t i = 0; i < str.size(); ++i) {
        if (str[i] == c) return i;
    }
    return std::nullopt;  // Kein Wert
}
int main() {
    auto result = findIndex("hello", 'l');
    if (result.has_value()) {
        std::cout << "Found at: " << *result << "\n";  // 2
    }
}

std::variant<Types...> ist eine typsichere Union, die zu jedem Zeitpunkt genau einen der angegebenen Typen enthält:

#include <iostream>
#include <variant>
#include <string>
int main() {
    std::variant<int, double, std::string> data;
    data = 42;
    std::cout << std::get<int>(data) << "\n";
    data = "hello";
    if (std::holds_alternative<std::string>(data)) {
        std::cout << std::get<std::string>(data) << "\n";
    }
}

std::any kann einen Wert eines beliebigen Typs aufnehmen, der zur Laufzeit bestimmt wird. Verwende std::any_cast, um den Wert abzurufen:

#include <iostream>
#include <any>
int main() {
    std::any value = 10;
    std::cout << std::any_cast<int>(value) << "\n";
    value = std::string("text");
    std::cout << std::any_cast<std::string>(value) << "\n";
}

Wähle std::optional für nullable Werte, std::variant, wenn du die möglichen Typen zur Kompilierzeit kennst, und std::any nur, wenn du wirklich Flexibilität bei den Laufzeittypen benötigst.

challenge icon

Aufgabe

Einfach

Erstellen wir ein Konfigurationssystem, das die Leistungsfähigkeit moderner C++-Vokabulartypen demonstriert. Du erstellst einen flexiblen Einstellungsmanager, der Werte verarbeitet, die fehlen können, unterschiedliche Typen enthalten können oder zur Laufzeit flexible Typen benötigen.

Du organisierst deinen Code auf drei Dateien:

  • ConfigTypes.h: Definiere deine Konfigurationswerttypen und Hilfsfunktionen.

    Erstelle eine Funktion namens parseValue, die einen String entgegennimmt und versucht, ihn als Ganzzahl zu analysieren. Wenn der String eine gültige Ganzzahl darstellt, gib die Ganzzahl, verpackt in std::optional, zurück. Wenn die Analyse fehlschlägt (der String nicht numerische Zeichen enthält), gib std::nullopt zurück. Der Einfachheit halber gilt ein String als gültig, wenn er nur Ziffern (und optional ein führendes Minuszeichen) enthält.

    Erstelle außerdem einen Typalias namens Setting mithilfe von std::variant, der entweder ein int, ein double oder einen std::string enthalten kann.

    Erstelle schließlich eine Funktion namens describeSetting, die ein Setting entgegennimmt und einen String zurückgibt, der beschreibt, welchen Typ er enthält und welchen Wert er hat, und zwar in diesem Format:

    • Für int: Integer: [value]
    • Für double: Double: [value]
    • Für string: String: [value]
  • DynamicStore.h: Erstelle einen einfachen Schlüssel-Wert-Speicher mithilfe von std::any.

    Definiere eine Klasse DynamicStore, die Werte beliebigen Typs speichern kann. Sie sollte Folgendes enthalten:

    • Eine Methode set, die einen String-Schlüssel und einen std::any-Wert entgegennimmt und beide intern speichert (verwende eine std::map)
    • Eine Methode get, die einen Schlüssel entgegennimmt und den std::any-Wert zurückgibt (gib ein leeres std::any zurück, wenn der Schlüssel nicht existiert)
    • Eine Methode hasKey, die true zurückgibt, wenn der Schlüssel existiert
  • main.cpp: Lies drei Eingaben ein:
    1. Einen String, der eine Zahl sein könnte (zum Testen von std::optional)
    2. Einen Typindikator: int, double oder string
    3. Einen Wert, der diesem Typ entspricht

    Demonstriere alle drei Vokabulartypen:

    Verwende zunächst deine Funktion parseValue mit der ersten Eingabe. Wenn sie einen Wert enthält, gib Parsed: [value] aus. Wenn sie leer ist, gib Parse failed aus.

    Erstelle zweitens eine Setting-Variante auf Grundlage des Typindikators. Wenn der Typ int ist, speichere den Wert als Ganzzahl. Wenn er double ist, speichere ihn als double. Wenn er string ist, speichere ihn als String. Rufe anschließend describeSetting auf und gib das Ergebnis aus.

    Erstelle drittens einen DynamicStore, speichere den String "config_loaded" unter dem Schlüssel "status", rufe ihn anschließend ab und gib Folgendes aus: Status: [value]. Verwende std::any_cast<std::string>, um den Wert zu extrahieren.

Beispielsweise bei den Eingaben 42, int und 100:

Parsed: 42
Integer: 100
Status: config_loaded

Bei den Eingaben hello, double und 3.14:

Parse failed
Double: 3.14
Status: config_loaded

Bei den Eingaben -15, string und username:

Parsed: -15
String: username
Status: config_loaded

Denke daran, die entsprechenden Header einzubinden: <optional>, <variant>, <any>, <string> und <map>, wo sie benötigt werden. Verwende std::holds_alternative und std::get, um mit deiner Variante zu arbeiten, und has_value() oder eine direkte boolesche Konvertierung, um dein Optional zu prüfen.

Probier es selbst

#include <iostream>
#include <string>
#include <any>
#include "ConfigTypes.h"
#include "DynamicStore.h"

int main() {
    // Lies drei Eingaben
    std::string input1;  // String, der eine Zahl sein könnte (zum Testen von std::optional)
    std::string typeIndicator;  // Typindikator: int, double oder string
    std::string value;  // Wert, der diesem Typ entspricht
    
    std::cin >> input1;
    std::cin >> typeIndicator;
    std::cin >> value;
    
    // TODO: Teil 1 - Teste std::optional mit parseValue
    // Verwende die parseValue-Funktion mit input1
    // Wenn es einen Wert enthält, gib "Parsed: [value]" aus
    // If it's empty, print "Parse failed"
    
    
    // TODO: Teil 2 - Teste std::variant mit Setting
    // Erstelle eine Setting-Variante basierend auf typeIndicator
    // Wenn der Typ "int" ist, speichere value als integer
    // Wenn der Typ "double" ist, speichere value als double
    // Wenn der Typ "string" ist, speichere value als string
    // Dann rufe describeSetting auf und gib das Ergebnis aus
    
    
    // TODO: Teil 3 - Teste std::any mit DynamicStore
    // Erstelle einen DynamicStore
    // Speichere den String "config_loaded" unter dem Schlüssel "status"
    // Rufe ihn ab und gib aus: "Status: [value]"
    // Verwende std::any_cast<std::string>, um den Wert zu extrahieren
    
    
    return 0;
}
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung

Übe selbstständig: Online-C++-Compiler