optional, variant, any
Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy — урок 89 из 104.
Современный C++ предоставляет три типа-словаря в заголовочных файлах <optional>, <variant> и <any>, которые помогают обрабатывать значения, которые могут отсутствовать, содержать один из нескольких типов или хранить любой тип вообще.
std::optional<T> представляет значение, которое может существовать или не существовать — идеально подходит для функций, которые могут завершиться неудачей без генерации исключения:
#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; // Значение отсутствует
}
int main() {
auto result = findIndex("hello", 'l');
if (result.has_value()) {
std::cout << "Found at: " << *result << "\n"; // 2
}
}std::variant<Types...> — это типобезопасное объединение (union), которое в любой момент времени содержит ровно один из указанных типов:
#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 может хранить значение любого типа, определяемого во время выполнения. Используйте std::any_cast для извлечения значения:
#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";
}Выбирайте std::optional для значений, которые могут отсутствовать, std::variant, когда вы знаете возможные типы на этапе компиляции, и std::any только тогда, когда вам действительно нужна гибкость типов во время выполнения.
Задание
ЛегкоДавайте создадим систему конфигурации, которая демонстрирует возможности современных словарных типов C++. Вы создадите гибкий менеджер настроек, который обрабатывает значения, которые могут отсутствовать, могут иметь разные типы или требуют гибкости типов во время выполнения.
Вы организуете свой код в трех файлах:
ConfigTypes.h: Определите типы значений конфигурации и вспомогательные функции.Создайте функцию с именем
parseValue, которая принимает строку и пытается преобразовать ее в целое число. Если строка представляет собой допустимое целое число, верните это число, обернутое вstd::optional. Если парсинг не удался (строка содержит нецифровые символы), вернитеstd::nullopt. Для простоты считайте строку допустимой, если она содержит только цифры (и, возможно, ведущий знак минуса).Также создайте псевдоним типа с именем
Setting, используяstd::variant, который может хранить либоint, либоdouble, либоstd::string.Наконец, создайте функцию с именем
describeSetting, которая принимаетSettingи возвращает строку, описывающую тип, который он хранит, и его значение в следующем формате:- Для int:
Integer: [value] - Для double:
Double: [value] - Для string:
String: [value]
- Для int:
DynamicStore.h: Создайте простое хранилище «ключ-значение», используяstd::any.Определите класс
DynamicStore, который может хранить значения любого типа. Он должен иметь:- Метод
set, который принимает строковый ключ и значениеstd::any, сохраняя их внутри (используйтеstd::map) - Метод
get, который принимает ключ и возвращает значениеstd::any(возвращает пустойstd::any, если ключ не существует) - Метод
hasKey, который возвращает true, если ключ существует
- Метод
main.cpp: Считайте три входных значения:- Строка, которая может быть числом (для тестирования
std::optional) - Индикатор типа:
int,doubleилиstring - Значение, соответствующее этому типу
Продемонстрируйте все три словарных типа:
Во-первых, используйте функцию
parseValueс первым входным значением. Если она содержит значение, выведитеParsed: [value]. Если она пуста, выведитеParse failed.Во-вторых, создайте вариант
Settingна основе индикатора типа. Если тип —int, сохраните значение как целое число. Еслиdouble, сохраните его как double. Еслиstring, сохраните его как строку. Затем вызовитеdescribeSettingи выведите результат.В-третьих, создайте
DynamicStore, сохраните строку"config_loaded"под ключом"status", затем извлеките ее и выведите:Status: [value]. Используйтеstd::any_cast<std::string>для извлечения значения.- Строка, которая может быть числом (для тестирования
Например, при входных данных 42, int и 100:
Parsed: 42
Integer: 100
Status: config_loadedПри входных данных hello, double и 3.14:
Parse failed
Double: 3.14
Status: config_loadedПри входных данных -15, string и username:
Parsed: -15
String: username
Status: config_loadedНе забудьте включить соответствующие заголовки: <optional>, <variant>, <any>, <string> и <map> там, где это необходимо. Используйте std::holds_alternative и std::get для работы с вашим вариантом, а также has_value() или прямое логическое преобразование для проверки вашего optional.
Шпаргалка
Современный C++ предоставляет три словарных типа для работы с гибкими значениями:
std::optional<T>
Представляет значение, которое может существовать или отсутствовать. Подключите <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; // No value
}
auto result = findIndex("hello", 'l');
if (result.has_value()) {
std::cout << *result; // Dereference to get value
}std::variant<Types...>
Типобезопасное объединение (union), которое содержит ровно один из указанных типов. Подключите <variant>:
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
Может содержать значение любого типа, определяемого во время выполнения. Подключите <any>:
std::any value = 10;
std::cout << std::any_cast<int>(value);
value = std::string("text");
std::cout << std::any_cast<std::string>(value);Когда использовать: std::optional для обнуляемых (nullable) значений, std::variant, когда возможные типы известны на этапе компиляции, std::any для гибкости типов во время выполнения.
Попробуйте сами
#include <iostream>
#include <string>
#include <any>
#include "ConfigTypes.h"
#include "DynamicStore.h"
int main() {
// Считать три входных значения
std::string input1; // Строка, которая может быть числом (для тестирования std::optional)
std::string typeIndicator; // Индикатор типа: int, double или string
std::string value; // Значение, соответствующее этому типу
std::cin >> input1;
std::cin >> typeIndicator;
std::cin >> value;
// TODO: Часть 1 — Тестирование std::optional с помощью parseValue
// Используйте функцию parseValue с input1
// Если она содержит значение, выведите "Parsed: [value]"
// Если она пуста, выведите "Parse failed"
// TODO: Часть 2 — Тестирование std::variant с помощью Setting
// Создайте вариант Setting на основе typeIndicator
// Если тип — "int", сохраните значение как целое число
// Если тип — "double", сохраните значение как число с плавающей запятой
// Если тип — "string", сохраните значение как строку
// Затем вызовите describeSetting и выведите результат
// TODO: Часть 3 — Тестирование std::any с помощью DynamicStore
// Создайте DynamicStore
// Сохраните строку "config_loaded" под ключом "status"
// Извлеките её и выведите: "Status: [value]"
// Используйте std::any_cast<std::string> для извлечения значения
return 0;
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыСборка и компиляция C++Заголовочные файлы и файлы исходного кодаПространства имен и область видимостиВведение в ООП на C++Классы и объектыУказатель 'this'Методы (функции-члены)Атрибуты (члены данных)Основы конструкторов и деструкторовИтоги — Простой калькулятор4Свойства классов
Члены экземпляра против статическихГеттеры и сеттерыКонстантные функции-членыКлючевое слово mutableСтатические методы и переменныеДружественные функции и классыИтоги — Менеджер банковских счетов7Наследование
Основы наследованияУровни доступа при наследованииПорядок вызова конструкторов и деструкторовПереопределение методовВиртуальные функции и VTableМножественное наследованиеВиртуальное наследованиеИтоги — Иерархия сотрудников10Обзор STL
Обзор и философия STLКонтейнеры STLИтераторыАлгоритмы STLФункторы и лямбда-выраженияИтоги: частота слов13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonFactory и Abstract FactoryПаттерн BuilderПаттерн ObserverПаттерн Strategy2Управление памятью
Стек и кучаУказатели и ссылкиДинамическая память (new/delete)Умные указатели в C++RAII в C++Итоги — Менеджер динамического массива5Инкапсуляция
Спецификаторы доступа в C++Спецификаторы доступа: подробный разборСокрытие данныхStruct против ClassВложенные и внутренние классыИтоги — Система учета студентов8Полиморфизм
Полиморфизм компиляции и времени выполненияПерегрузка функцийВиртуальные функции: повторениеЧистые виртуальные функцииАбстрактные классыПроектирование интерфейсов на C++Dynamic Casting и RTTIИтоги: Калькулятор фигур11Продвинутые концепции ООП
Композиция против наследованияМиксины через CRTPИдиома PimplСтирание типовEnum Classes и строгая типизацияОбработка исключений в ООППользовательские иерархии исключений14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикRAII как паттерн3Конструкторы и деструкторы
Конструктор по умолчаниюПараметризованный конструкторКонструктор копированияКонструктор перемещенияСписки инициализации конструктораДелегирующие конструкторыГлубокое погружение в деструкторыПравило трех / пяти / нуляПовторение — класс String6Перегрузка операторов
Введение в перегрузку операторовПерегрузка арифметических операторовПерегрузка операторов сравненияОператоры потоковПерегрузка оператора присваиванияПерегрузка операторов [] и ()Операторы преобразования типовИтоги — класс Matrix9Шаблоны
Шаблоны функцийШаблоны классовСпециализация шаблоновВариативные шаблоныОсновы SFINAE и Type TraitsИтоги — Обобщенный контейнер12Возможности современного C++
Семантика перемещения и RvaluesИдеальная передача (Perfect Forwarding)Лямбда-выражения в деталяхstd::function и std::bindconstexpr и constevalСтруктурированные привязкиoptional, variant, any