optional, variant, any
Część sekcji Programowanie obiektowe ścieżki C++ w Coddy. Lekcja 89 z 104.
Nowoczesny C++ udostępnia trzy typy słownikowe w nagłówkach <optional>, <variant> i <any>, które pomagają obsługiwać wartości, które mogą być nieobecne, przechowywać jedną z kilku typów lub dowolny typ.
std::optional<T> reprezentuje wartość, która może istnieć lub nie — idealne rozwiązanie dla funkcji, które mogą się nie powieść bez zgłaszania wyjątku:
#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; // Brak wartości
}
int main() {
auto result = findIndex("hello", 'l');
if (result.has_value()) {
std::cout << "Found at: " << *result << "\n"; // 2
}
}std::variant<Types...> to bezpieczna typowo unia, która w danej chwili przechowuje dokładnie jedną z określonych typów:
#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 może przechowywać wartość dowolnego typu, określonego w czasie działania. Użyj std::any_cast, aby pobrać wartość:
#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";
}Wybierz std::optional dla wartości dopuszczających brak wartości, std::variant, gdy znasz możliwe typy w czasie kompilacji, a std::any tylko wtedy, gdy naprawdę potrzebujesz elastyczności typów w czasie wykonywania.
Wyzwanie
ŁatwyStwórzmy system konfiguracji, który pokaże możliwości nowoczesnych typów słownikowych C++. Utworzysz elastyczny menedżer ustawień obsługujący wartości, których może brakować, które mogą mieć różne typy lub których typ trzeba elastycznie określać w czasie działania programu.
Rozmieścisz kod w trzech plikach:
ConfigTypes.h: Zdefiniuj typy wartości konfiguracji i funkcje pomocnicze.Utwórz funkcję o nazwie
parseValue, która przyjmuje ciąg znaków i próbuje sparsować go jako liczbę całkowitą. Jeśli ciąg znaków przedstawia prawidłową liczbę całkowitą, zwróć ją opakowaną wstd::optional. Jeśli parsowanie się nie powiedzie (ciąg znaków zawiera znaki nienumeryczne), zwróćstd::nullopt. Dla uproszczenia uznaj ciąg znaków za prawidłowy, jeśli zawiera wyłącznie cyfry (i opcjonalnie początkowy znak minus).Utwórz również alias typu o nazwie
Setting, używającstd::variant, który może przechowywać wartość typuint,doublelubstd::string.Na koniec utwórz funkcję o nazwie
describeSetting, która przyjmuje wartość typuSettingi zwraca ciąg znaków opisujący przechowywany typ i jego wartość w następującym formacie:- Dla int:
Integer: [value] - Dla double:
Double: [value] - Dla string:
String: [value]
- Dla int:
DynamicStore.h: Utwórz prosty magazyn klucz-wartość, używającstd::any.Zdefiniuj klasę
DynamicStore, która może przechowywać wartości dowolnego typu. Powinna mieć:- Metodę
set, która przyjmuje klucz w postaci ciągu znaków i wartość typustd::any, a następnie przechowuje je wewnętrznie (użyjstd::map) - Metodę
get, która przyjmuje klucz i zwraca wartość typustd::any> (zwróć pusty obiekt <code>std::any, jeśli klucz nie istnieje) - Metodę
hasKey, która zwraca true, jeśli klucz istnieje
- Metodę
main.cpp: Wczytaj trzy dane wejściowe:- Ciąg znaków, który może być liczbą (do przetestowania
std::optional) - Wskaźnik typu:
int,doublelubstring - Wartość odpowiadającą temu typowi
Zademonstruj wszystkie trzy typy słownikowe:
Najpierw użyj funkcji
parseValuez pierwszą daną wejściową. Jeśli zawiera wartość, wypiszParsed: [value]. Jeśli jest pusta, wypiszParse failed.Następnie utwórz wariant
Settingna podstawie wskaźnika typu. Jeśli typ toint, przechowaj wartość jako liczbę całkowitą. Jeśli todouble, przechowaj ją jako liczbę zmiennoprzecinkową typu double. Jeśli tostring, przechowaj ją jako ciąg znaków. Potem wywołajdescribeSettingi wypisz wynik.Na koniec utwórz obiekt
DynamicStore, przechowaj ciąg znaków"config_loaded"pod kluczem"status", a następnie pobierz go i wypisz:Status: [value]. Użyjstd::any_cast<std::string>, aby wyodrębnić wartość.- Ciąg znaków, który może być liczbą (do przetestowania
Na przykład dla danych wejściowych 42, int i 100:
Parsed: 42
Integer: 100
Status: config_loadedDla danych wejściowych hello, double i 3.14:
Parse failed
Double: 3.14
Status: config_loadedDla danych wejściowych -15, string i username:
Parsed: -15
String: username
Status: config_loadedPamiętaj, aby dołączyć odpowiednie nagłówki: <optional>, <variant>, <any>, <string> i <map>, tam gdzie są potrzebne. Użyj std::holds_alternative i std::get, aby pracować z wariantem, oraz has_value() lub bezpośredniej konwersji na wartość logiczną, aby sprawdzić optional.
Spróbuj swoich sił
#include <iostream>
#include <string>
#include <any>
#include "ConfigTypes.h"
#include "DynamicStore.h"
int main() {
// Wczytaj trzy dane wejściowe
std::string input1; // Łańcuch znaków, który może być liczbą (do testowania std::optional)
std::string typeIndicator; // Wskaźnik typu: int, double lub string
std::string value; // Wartość odpowiadająca temu typowi
std::cin >> input1;
std::cin >> typeIndicator;
std::cin >> value;
// TODO: Część 1 - Przetestuj std::optional za pomocą parseValue
// Użyj funkcji parseValue z input1
// Jeśli zawiera wartość, wypisz "Parsed: [value]"
// Jeśli jest puste, wypisz "Parse failed"
// TODO: Część 2 - Przetestuj std::variant z Setting
// Utwórz wariant Setting na podstawie typeIndicator
// Jeśli typ to "int", zapisz wartość jako liczbę całkowitą
// Jeśli typ to "double", zapisz wartość jako liczbę typu double
// Jeśli typ to "string", zapisz wartość jako ciąg znaków
// Następnie wywołaj describeSetting i wypisz wynik
// TODO: Część 3 - Przetestuj std::any z DynamicStore
// Utwórz obiekt DynamicStore
// Zapisz ciąg znaków "config_loaded" pod kluczem "status"
// Odczytaj go i wypisz: "Status: [value]"
// Użyj std::any_cast<std::string>, aby wyodrębnić wartość
return 0;
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy programowania obiektowego
Pliki zewnętrzneBudowanie i kompilacja C++Pliki nagłówkowe i pliki źródłowePrzestrzenie nazw i zakresWprowadzenie do programowania obiektowego w C++Klasy a obiektyWskaźnik „this”Metody (funkcje składowe)Atrybuty (składowe danych)Podstawy konstruktorów i destruktorówPowtórzenie – prosty kalkulator4Właściwości klas
Elementy instancji a elementy statyczneGettery i setteryStałe funkcje składoweSłowo kluczowe mutableMetody i zmienne statyczneFunkcje i klasy zaprzyjaźnionePodsumowanie – menedżer konta bankowego7Dziedziczenie
Podstawy dziedziczeniaPoziomy dostępu w dziedziczeniuKolejność wywołań konstruktora i destruktoraNadpisywanie metodFunkcje wirtualne i VTableWielokrotne dziedziczenieDziedziczenie wirtualnePowtórzenie — hierarchia pracowników2Zarządzanie pamięcią
Pamięć stosu a stertyWskaźniki i referencjePamięć dynamiczna (new/delete)Inteligentne wskaźniki w C++RAII w C++Podsumowanie — menedżer tablicy dynamicznej5Hermetyzacja
Specyfikatory dostępu w C++Specyfikatory dostępu — szczegółowoUkrywanie informacjiStruktura a klasaKlasy zagnieżdżone i wewnętrznePodsumowanie — system ewidencji studentów8Polimorfizm
Polimorfizm czasu kompilacji i wykonaniaPrzeciążanie funkcjiFunkcje wirtualne — powtórkaCzysto wirtualne funkcjeKlasy abstrakcyjneProjektowanie interfejsów w C++Rzutowanie dynamiczne i RTTIPodsumowanie — kalkulator kształtów3Konstruktory i destruktory
Konstruktor domyślnyKonstruktor z parametramiKonstruktor kopiującyKonstruktor przenoszącyListy inicjalizacyjne konstruktoraKonstruktory delegująceDestruktor — szczegółowe omówienieZasada trzech / pięciu / zeraPodsumowanie — klasa String6Przeciążanie operatorów
Wprowadzenie do przeciążania operatorówPrzeciążanie operatorów arytmetycznychPrzeciążanie operatorów porównaniaOperatory strumieniowePrzeciążanie operatora przypisaniaPrzeciążanie operatorów [] i ()Operatory konwersji typówPodsumowanie — klasa Matrix9Szablony
Szablony funkcjiSzablony klasSpecjalizacja szablonówSzablony wariadycznePodstawy SFINAE i cech typówPodsumowanie — kontener generyczny12Nowoczesne funkcje C++
Semantyka przenoszenia i r-wartościDoskonałe przekazywanieWyrażenia lambda w szczegółachstd::function i std::bindconstexpr i constevalWiązania strukturalneoptional, variant, anyPoćwicz samodzielnie: Kompilator C++ online