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.
Aufgabe
EinfachErstellen 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 instd::optional, zurück. Wenn die Analyse fehlschlägt (der String nicht numerische Zeichen enthält), gibstd::nulloptzurü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
Settingmithilfe vonstd::variant, der entweder einint, eindoubleoder einenstd::stringenthalten kann.Erstelle schließlich eine Funktion namens
describeSetting, die einSettingentgegennimmt 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]
- Für int:
DynamicStore.h: Erstelle einen einfachen Schlüssel-Wert-Speicher mithilfe vonstd::any.Definiere eine Klasse
DynamicStore, die Werte beliebigen Typs speichern kann. Sie sollte Folgendes enthalten:- Eine Methode
set, die einen String-Schlüssel und einenstd::any-Wert entgegennimmt und beide intern speichert (verwende einestd::map) - Eine Methode
get, die einen Schlüssel entgegennimmt und denstd::any-Wert zurückgibt (gib ein leeresstd::anyzurück, wenn der Schlüssel nicht existiert) - Eine Methode
hasKey, die true zurückgibt, wenn der Schlüssel existiert
- Eine Methode
main.cpp: Lies drei Eingaben ein:- Einen String, der eine Zahl sein könnte (zum Testen von
std::optional) - Einen Typindikator:
int,doubleoderstring - Einen Wert, der diesem Typ entspricht
Demonstriere alle drei Vokabulartypen:
Verwende zunächst deine Funktion
parseValuemit der ersten Eingabe. Wenn sie einen Wert enthält, gibParsed: [value]aus. Wenn sie leer ist, gibParse failedaus.Erstelle zweitens eine
Setting-Variante auf Grundlage des Typindikators. Wenn der Typintist, speichere den Wert als Ganzzahl. Wenn erdoubleist, speichere ihn als double. Wenn erstringist, speichere ihn als String. Rufe anschließenddescribeSettingauf 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]. Verwendestd::any_cast<std::string>, um den Wert zu extrahieren.- Einen String, der eine Zahl sein könnte (zum Testen von
Beispielsweise bei den Eingaben 42, int und 100:
Parsed: 42
Integer: 100
Status: config_loadedBei den Eingaben hello, double und 3.14:
Parse failed
Double: 3.14
Status: config_loadedBei den Eingaben -15, string und username:
Parsed: -15
String: username
Status: config_loadedDenke 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;
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der OOP
Externe DateienC++ Build & KompilierungHeader-Dateien & QuelldateienNamespaces & GültigkeitsbereichEinführung in OOP mit C++Klassen vs. ObjekteDer „this“-ZeigerMethoden (Member-Funktionen)Attribute (Datenmember)Grundlagen zu Ctors & DtorsRückblick – Einfacher Taschenrechner4Klassenattribute
Instanz- vs. statische MemberGetter und SetterConst-MemberfunktionenMutable-SchlüsselwortStatische Methoden und VariablenFriend-Funktionen und -KlassenZusammenfassung – Bankkontoverwaltung7Vererbung
Grundlagen der VererbungZugriffsebenen bei der VererbungAufrufreihenfolge von Ctor & DtorÜberschreiben von MethodenVirtuelle Funktionen & VTableMehrfachvererbungVirtuelle VererbungRückblick – Mitarbeiterhierarchie2Speicherverwaltung
Stack- vs. Heap-SpeicherZeiger und ReferenzenDynamischer Speicher (new/delete)Intelligente Zeiger in C++RAII in C++Rückblick – Dynamischer Array-Manager5Kapselung
Zugriffsbezeichner in C++Zugriffsbezeichner im DetailInformationsverbergungStruct vs. KlasseVerschachtelte und innere KlassenWiederholung – Studierendenverwaltungssystem8Polymorphie
Polymorphie zur Compilezeit vs. LaufzeitFunktionsüberladungVirtuelle Funktionen erneut betrachtetRein virtuelle FunktionenAbstrakte KlassenInterface-Design in C++Dynamic Casting & RTTIRückblick – Shape-Calculator3Konstruktoren & Destruktoren
StandardkonstruktorParametrisierter KonstruktorKopierkonstruktorMove-KonstruktorKonstruktor-InitialisierungslistenDelegierende KonstruktorenDestruktor im DetailDrei-/Fünf-/Null-RegelZusammenfassung – String-Klasse6Operatorüberladung
Einführung in die OperatorüberladungArithmetische OperatorüberladungVergleichsoperatorüberladungStream-OperatorenZuweisungsoperatorüberladungÜberladung der Operatoren [] und ()TypkonvertierungsoperatorenWiederholung – Matrix-Klasse9Templates
FunktionstemplatesKlassen-TemplatesTemplate-SpezialisierungVariadische TemplatesGrundlagen zu SFINAE und Type TraitsRückblick – Generischer Container12Moderne C++-Funktionen
Move-Semantik & RvaluesPerfektes ForwardingLambda-Ausdrücke im Detailstd::function & std::bindconstexpr und constevalStrukturierte Bindungenoptional, variant, anyÜbe selbstständig: Online-C++-Compiler