Как использовать std::optional, std::variant и std::any в C++

Автор: | 15 июня, 2025

овременный C++ предлагает мощные инструменты для работы с неопределенными значениями и переменными, способными хранить данные разных типов. В этой статье мы рассмотрим три ключевых компонента стандартной библиотеки: std::optional, std::variant и std::any. Они пришли в C++17 и кардинально упростили работу с переменными, чье состояние или тип могут меняться во время выполнения программы.


1. std::optional: значение или отсутствие значения

Что это такое?

std::optional<T> представляет собой обертку над значением типа T, которая может либо содержать значение, либо быть пустой (т.е. не содержать ничего). Это безопасная альтернатива nullptr и другим флагам-пустышкам.

Когда использовать?

  • Когда функция может вернуть значение, а может и не вернуть (вместо nullptr или -1).
  • Когда нужно явно обозначить возможность отсутствия результата.

Пример:

#include <optional>
#include <iostream>

std::optional<int> divide(int a, int b) {
    if (b == 0) return std::nullopt; // нет значения
    return a / b;
}

int main() {
    auto result = divide(10, 2);
    if (result) {
        std::cout << "Результат: " << *result << '\n';
    } else {
        std::cout << "Деление на ноль!\n";
    }
}

Основные методы:

  • has_value() — есть ли значение.
  • operator*() или value() — получить значение (второй бросает исключение, если пустой).
  • value_or(default) — вернуть значение или значение по умолчанию.

2. std::variant: одно значение из нескольких типов

Что это такое?

std::variant — это тип, способный хранить одно значение из фиксированного набора типов.

Когда использовать?

  • Когда переменная может быть одного из нескольких типов (альтернатива union).
  • Для реализации паттерна «типизированный контейнер значений».

Пример:

#include <variant>
#include <iostream>

using VarType = std::variant<int, double, std::string>;

void print(const VarType& v) {
    std::visit([](auto&& arg) {
        std::cout << "Значение: " << arg << '\n';
    }, v);
}

int main() {
    VarType v = 42;
    print(v);

    v = 3.14;
    print(v);

    v = "Привет";
    print(v);
}

Основные особенности:

  • std::visit(visitor, variant) — для вызова соответствующей логики.
  • std::holds_alternative<T>(v) — проверка, является ли v значением типа T.
  • std::get<T>(v) — получить значение (бросает std::bad_variant_access при ошибке).
  • std::get_if<T>(&v) — безопасное получение указателя на значение типа T.

3. std::any: значение любого типа

Что это такое?

std::any — контейнер для хранения значения произвольного типа. Тип сохраняется, и можно безопасно попытаться извлечь его обратно.

Когда использовать?

  • Когда тип значения неизвестен во время компиляции.
  • Для универсальных контейнеров или интерфейсов.

Пример:

#include <any>
#include <iostream>
#include <string>

int main() {
    std::any value;
    value = 10;

    try {
        std::cout << std::any_cast<int>(value) << '\n';
        value = std::string("Строка");
        std::cout << std::any_cast<std::string>(value) << '\n';
    } catch (const std::bad_any_cast& e) {
        std::cout << "Ошибка приведения: " << e.what() << '\n';
    }
}

Основные методы:

  • std::any_cast<T>(any) — безопасное приведение (бросает исключение при неудаче).
  • has_value() — проверка, хранит ли значение.
  • type() — возвращает std::type_info о текущем типе.
  • reset() — удаляет хранимое значение.

Сравнение

Характеристикаstd::optionalstd::variantstd::any
Кол-во типовОдин (или none)Один из фиксированного набораЛюбой тип
Проверка типа в runtimeНетДа (через holds_alternative)Да (через type() и any_cast)
Безопасность типовПолнаяПолнаяМожет бросить исключение
ПрименениеМожет быть или не бытьОдин из нескольких типовЛюбой тип, определяемый в runtime

Заключение

std::optional, std::variant и std::any — мощные инструменты, которые помогают сделать код более безопасным, читаемым и выразительным. Выбор между ними зависит от задачи:

  • Используйте std::optional, если нужно обозначить возможность отсутствия значения.
  • Используйте std::variant, если переменная может быть одного из нескольких типов.
  • Используйте std::any, если заранее неизвестно, какой тип нужно будет хранить.