Menu
Coddy logo textTech

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.

challenge icon

Wyzwanie

Łatwy

Stwó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ą w std::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ąc std::variant, który może przechowywać wartość typu int, double lub std::string.

    Na koniec utwórz funkcję o nazwie describeSetting, która przyjmuje wartość typu Setting i 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]
  • DynamicStore.h: Utwórz prosty magazyn klucz-wartość, używając std::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ść typu std::any, a następnie przechowuje je wewnętrznie (użyj std::map)
    • Metodę get, która przyjmuje klucz i zwraca wartość typu std::any> (zwróć pusty obiekt <code>std::any, jeśli klucz nie istnieje)
    • Metodę hasKey, która zwraca true, jeśli klucz istnieje
  • main.cpp: Wczytaj trzy dane wejściowe:
    1. Ciąg znaków, który może być liczbą (do przetestowania std::optional)
    2. Wskaźnik typu: int, double lub string
    3. Wartość odpowiadającą temu typowi

    Zademonstruj wszystkie trzy typy słownikowe:

    Najpierw użyj funkcji parseValue z pierwszą daną wejściową. Jeśli zawiera wartość, wypisz Parsed: [value]. Jeśli jest pusta, wypisz Parse failed.

    Następnie utwórz wariant Setting na podstawie wskaźnika typu. Jeśli typ to int, przechowaj wartość jako liczbę całkowitą. Jeśli to double, przechowaj ją jako liczbę zmiennoprzecinkową typu double. Jeśli to string, przechowaj ją jako ciąg znaków. Potem wywołaj describeSetting i 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żyj std::any_cast<std::string>, aby wyodrębnić wartość.

Na przykład dla danych wejściowych 42, int i 100:

Parsed: 42
Integer: 100
Status: config_loaded

Dla danych wejściowych hello, double i 3.14:

Parse failed
Double: 3.14
Status: config_loaded

Dla danych wejściowych -15, string i username:

Parsed: -15
String: username
Status: config_loaded

Pamię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;
}
quiz iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator C++ online