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...> — это типобезопасное объединение, которое в любой момент содержит ровно один из указанных типов:
#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 для допускающих значение null, 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— сохраните его как число с плавающей точкой. Если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.
Попробуйте сами
#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]"
// If it's empty, print "Parse failed"
// TODO: Часть 2 - Тест std::variant с Setting
// Создать вариант Setting на основе typeIndicator
// Если тип — "int", сохранить value как целое число
// Если тип — "double", сохранить value как double
// Если тип — "string", сохранить value как 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Потренируйтесь самостоятельно: Онлайн-компилятор C++