Перейти к содержанию

Словарные типы: optional, variant и tuple

Начало урока

Словарные типы: optional, variant и tuple

Типобезопасные средства для "возможно есть значение", "один из нескольких типов" и группировки

Главная идея: В стандартной библиотеке есть несколько небольших универсальных типов, которые встречаются в сигнатурах бесчисленных реальных библиотек. Их называют словарными типами, потому что они дают всем общий словарь для типичных форм данных: "значение, которого может не быть", "значение, которое является одним из нескольких типов" и "фиксированный набор из нескольких значений". Именно так современный C++ заменяет старые, подверженные ошибкам приёмы: значения-заглушки, выходные параметры и сырой union.
К концу вы сможете:
  • использовать std::optional<T> для значения, которое может присутствовать или отсутствовать,
  • использовать std::variant<A, B, ...> как типобезопасное объединение, всегда знающее, что в нём хранится,
  • группировать несколько значений с помощью std::pair и std::tuple,
  • аккуратно распаковывать любой из них с помощью структурных привязок.

Эта тема предполагает, что вы уверенно владеете шаблонами, ссылками и семантикой перемещения из предыдущих разделов курса. Все примеры компилируются компилятором C++17.

1. std::optional - возможно, значение

Функции часто нужно сказать "вот результат" или "результата нет". Старые способы выразить это ненадёжны: вернуть магическую заглушку вроде -1 (а если -1 - допустимый ответ?), вернуть bool и записать настоящий ответ через выходной параметр или вернуть сырой указатель, который может быть nullptr (кто им владеет?). std::optional<T> (из <optional>) делает "отсутствие" полноценным, проверяемым типом состоянием.

optional<T> либо содержит T, либо пуст. Вы проверяете его как bool, читаете значение через * или -> (только когда оно есть) и задаёте запасной вариант через value_or. Пустое состояние записывается как std::nullopt.

#include <optional>
#include <string>
using namespace std;

optional<int> parsePort(const string& scheme) {
    if (scheme == "http")  return 80;
    if (scheme == "https") return 443;
    return nullopt;                 // нет разумного значения
}

Вызывающий не может забыть проверить, потому что значение находится внутри optional и его сначала нужно открыть:

auto p = parsePort("https");
if (p) {                            // есть значение?
    cout << "port = " << *p << "\n";  // безопасно: мы проверили
}
int port = parsePort("ftp").value_or(-1);  // запасной вариант, если пусто
Разыменование пустого optional через * или -> - это неопределённое поведение, ровно как разыменование нулевого указателя. Всегда сначала проверяйте или используйте value_or. Если предпочитаете исключение, вызовите .value(), который бросает std::bad_optional_access, когда значения нет.

Продолжите этот урок на YourAIZone

В полном уроке — объяснение целиком, разобранные примеры, частые ошибки, тесты с мгновенной проверкой и ИИ-репетитор. Начните сразу как гость или создайте аккаунт, чтобы сохранять прогресс.

В полном уроке

  • Объяснение целиком
  • Разобранные примеры
  • Частые ошибки и как их избежать
  • Тестовые вопросы: 15
  • ИИ-репетитор, который отвечает на вопросы по теме
  • Отслеживание прогресса для ученика, родителей и учителя