optional, variant, any
Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy — Lektion 89 von 104.
Modernes C++ bietet drei Vokabular-Typen in den <optional>, <variant> und <any> Headern an, die Ihnen helfen, Werte zu handhaben, die fehlen könnten, einen von mehreren Typen halten oder einen beliebigen Typ speichern können.
std::optional<T> repräsentiert einen Wert, der existieren kann oder auch nicht – ideal für Funktionen, die fehlschlagen könnten, ohne eine Exception 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 speichern, der zur Laufzeit bestimmt wird. Verwenden Sie 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ählen Sie std::optional für Nullwerte, std::variant, wenn Sie die möglichen Typen zur Kompilierzeit kennen, und std::any nur dann, wenn Sie wirklich Flexibilität bei den Typen zur Laufzeit benötigen.
Aufgabe
EinfachLassen Sie uns ein Konfigurationssystem erstellen, das die Leistungsfähigkeit moderner C++-Vokabular-Typen demonstriert. Sie werden einen flexiblen Einstellungsmanager erstellen, der Werte verarbeitet, die fehlen könnten, verschiedene Typen enthalten können oder Laufzeit-Typflexibilität benötigen.
Sie werden Ihren Code auf drei Dateien verteilen:
ConfigTypes.h: Definieren Sie Ihre Konfigurationswerttypen und Hilfsfunktionen.Erstellen Sie eine Funktion namens
parseValue, die einen String entgegennimmt und versucht, ihn als Ganzzahl zu parsen. Wenn der String eine gültige Ganzzahl darstellt, geben Sie die Ganzzahl verpackt in einstd::optionalzurück. Wenn das Parsen fehlschlägt (der String enthält nicht-numerische Zeichen), geben Siestd::nulloptzurück. Betrachten Sie der Einfachheit halber einen String als gültig, wenn er nur Ziffern (und optional ein führendes Minuszeichen) enthält.Erstellen Sie außerdem einen Typ-Alias namens
Settingunter Verwendung vonstd::variant, der entweder einint, eindoubleoder einstd::stringenthalten kann.Erstellen Sie schließlich eine Funktion namens
describeSetting, die einSettingentgegennimmt und einen String zurückgibt, der beschreibt, welchen Typ es enthält und welchen Wert es hat, in diesem Format:- Für int:
Integer: [value] - Für double:
Double: [value] - Für string:
String: [value]
- Für int:
DynamicStore.h: Erstellen Sie einen einfachen Key-Value-Speicher unter Verwendung vonstd::any.Definieren Sie eine Klasse
DynamicStore, die Werte eines beliebigen Typs speichern kann. Sie sollte Folgendes enthalten:- Eine Methode
set, die einen String-Schlüssel und einenstd::any-Wert entgegennimmt und diese intern speichert (verwenden Sie einestd::map) - Eine Methode
get, die einen Schlüssel entgegennimmt und denstd::any-Wert zurückgibt (geben Sie 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: Lesen Sie drei Eingaben ein:- Einen String, der eine Zahl sein könnte (zum Testen von
std::optional) - Einen Typ-Indikator:
int,doubleoderstring - Einen Wert, der diesem Typ entspricht
Demonstrieren Sie alle drei Vokabular-Typen:
Verwenden Sie zuerst Ihre Funktion
parseValuemit der ersten Eingabe. Wenn sie einen Wert enthält, geben SieParsed: [value]aus. Wenn sie leer ist, geben SieParse failedaus.Zweitens, erstellen Sie eine
Setting-Variante basierend auf dem Typ-Indikator. Wenn der Typintist, speichern Sie den Wert als Ganzzahl. Wenndouble, speichern Sie ihn als Double. Wennstring, speichern Sie ihn als String. Rufen Sie danndescribeSettingauf und geben Sie das Ergebnis aus.Drittens, erstellen Sie einen
DynamicStore, speichern Sie den String"config_loaded"unter dem Schlüssel"status", rufen Sie ihn dann ab und geben Sie aus:Status: [value]. Verwenden Siestd::any_cast<std::string>, um den Wert zu extrahieren.- Einen String, der eine Zahl sein könnte (zum Testen von
Zum Beispiel mit den Eingaben 42, int und 100:
Parsed: 42
Integer: 100
Status: config_loadedMit den Eingaben hello, double und 3.14:
Parse failed
Double: 3.14
Status: config_loadedMit den Eingaben -15, string und username:
Parsed: -15
String: username
Status: config_loadedDenken Sie daran, die entsprechenden Header einzubinden: <optional>, <variant>, <any>, <string> und <map>, wo sie benötigt werden. Verwenden Sie std::holds_alternative und std::get, um mit Ihrer Variante zu arbeiten, und has_value() oder eine direkte boolesche Konvertierung, um Ihr Optional zu prüfen.
Spickzettel
Modernes C++ bietet drei Vokabular-Typen für den Umgang mit flexiblen Werten:
std::optional<T>
Repräsentiert einen Wert, der existieren kann oder auch nicht. Binden Sie <optional> ein:
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; // No value
}
auto result = findIndex("hello", 'l');
if (result.has_value()) {
std::cout << *result; // Dereference to get value
}std::variant<Types...>
Eine typsichere Union, die genau einen der angegebenen Typen enthält. Binden Sie <variant> ein:
std::variant<int, double, std::string> data;
data = 42;
std::cout << std::get<int>(data);
data = "hello";
if (std::holds_alternative<std::string>(data)) {
std::cout << std::get<std::string>(data);
}std::any
Kann einen Wert eines beliebigen Typs halten, der zur Laufzeit bestimmt wird. Binden Sie <any> ein:
std::any value = 10;
std::cout << std::any_cast<int>(value);
value = std::string("text");
std::cout << std::any_cast<std::string>(value);Wann man sie verwendet: std::optional für Nullable-Werte, std::variant, wenn die möglichen Typen zur Kompilierzeit bekannt sind, std::any für Typflexibilität zur Laufzeit.
Probier es selbst
#include <iostream>
#include <string>
#include <any>
#include "ConfigTypes.h"
#include "DynamicStore.h"
int main() {
// Lies drei Eingaben ein
std::string input1; // String, der eine Zahl sein könnte (zum Testen von std::optional)
std::string typeIndicator; // Typ-Indikator: 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 Funktion parseValue mit input1
// Wenn sie einen Wert enthält, gib "Parsed: [value]" aus
// Wenn sie leer ist, gib "Parse failed" aus
// TODO: Teil 2 - Teste std::variant mit Setting
// Erstelle eine Setting-Variante basierend auf typeIndicator
// Wenn der Typ "int" ist, speichere den Wert als integer
// Wenn der Typ "double" ist, speichere den Wert als double
// Wenn der Typ "string" ist, speichere den Wert als string
// Rufe dann 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 & QuelldateienNamensräume & GültigkeitsbereicheEinführung in OOP in C++Klassen vs. ObjekteDer 'this'-PointerMethoden (Elementfunktionen)Attribute (Datenelemente)Grundlagen zu Ctors & DtorsRückblick - Einfacher Taschenrechner4Klasseneigenschaften
Instanz- vs. statische MemberGetter und SetterConst-MemberfunktionenMutable-SchlüsselwortStatische Methoden und VariablenFriend-Funktionen & KlassenZusammenfassung - Bankkonto-Manager7Vererbung
Grundlagen der VererbungZugriffsstufen bei VererbungAufrufreihenfolge von Ctor & DtorÜberschreiben von MethodenVirtuelle Funktionen & VTableMehrfachvererbungVirtuelle VererbungRückblick - Mitarbeiter-Hierarchie2Speicherverwaltung
Stack vs. Heap SpeicherZeiger und ReferenzenDynamischer Speicher (new/delete)Smart Pointers in C++RAII in C++Rückblick – Dynamischer Array-Manager5Kapselung
Zugriffsspezifizierer in C++Zugriffsspezifizierer im DetailInformation HidingStruct vs. ClassVerschachtelte & innere KlassenRückblick - Studentenverwaltungssystem8Polymorphie
Kompilierzeit- vs. Laufzeit-PolymorphieFunktionsüberladungVirtuelle Funktionen – WiederholungRein virtuelle FunktionenAbstrakte KlassenInterface-Design in C++Dynamic Casting & RTTIZusammenfassung – Formen-Rechner3Konstruktoren & Destruktoren
StandardkonstruktorParametrisierter KonstruktorKopierkonstruktorVerschiebekonstruktorKonstruktor-InitialisierungslistenDelegierende KonstruktorenDestruktoren im DetailRule of Three / Five / ZeroZusammenfassung - String-Klasse6Operatorüberladung
Einführung in die OperatorüberladungArithmetische OperatorüberladungVergleichsoperatorüberladungStream-OperatorenZuweisungsoperatorüberladungÜberladen der [] und () OperatorenTypumwandlungsoperatorenRückblick - Matrix-Klasse9Templates
FunktionstemplatesKlassentemplatesTemplate-SpezialisierungVariadische TemplatesSFINAE & Type Traits GrundlagenRückblick - Generische Container12Moderne C++ Features
Move-Semantik & RvaluesPerfect ForwardingLambda-Ausdrücke im Detailstd::function & std::bindconstexpr und constevalStructured Bindingsoptional, variant, any