std::variant в C++17: безопасная альтернатива union с практическими примерами
std::variant в C++17 — это контейнер из стандартной библиотеки, который в каждый момент времени хранит значение ровно одного типа из заданного списка. Проще говоря, переменная может быть либо int, либо std::string, либо, например, double, но не всем сразу.
Поисковый запрос, под который полезна эта статья: «std::variant C++17 примеры». Если вы уже знаете основы классов, функций и шаблонов, std::variant станет отличным следующим шагом к более безопасному современному C++.
Зачем нужен std::variant
Представьте, что программа читает настройки из файла. Значение настройки может быть числом, строкой или логическим значением:
max_users = 100app_name = "Demo"debug = true
Можно хранить всё строками, но тогда придётся постоянно вручную преобразовывать типы. Можно использовать наследование, но это часто слишком сложно для простой задачи. std::variant решает проблему аккуратно: список возможных типов известен заранее, а компилятор помогает не ошибиться.
Подключение и базовый пример
std::variant появился в C++17, поэтому компилировать код нужно с соответствующим стандартом: -std=c++17 или новее.
#include <iostream>
#include <string>
#include <variant>
int main() {
std::variant<int, std::string, double> value;
value = 10;
value = std::string("Hello");
value = 3.14;
std::cout << "Индекс текущего типа: " << value.index() << std::endl;
}
Метод index() возвращает номер текущего типа в списке. Для std::variant<int, std::string, double>: int имеет индекс 0, std::string — 1, double — 2.
Как безопасно получить значение
Для чтения значения используется std::get. Но важно: если запросить не тот тип, будет выброшено исключение std::bad_variant_access.
#include <iostream>
#include <string>
#include <variant>
int main() {
std::variant<int, std::string> data = std::string("C++");
try {
std::cout << std::get<std::string>(data) << std::endl;
std::cout << std::get<int>(data) << std::endl; // ошибка
} catch (const std::bad_variant_access& e) {
std::cout << "Неверный тип внутри variant" << std::endl;
}
}
Такой код работает, но в реальных проектах лучше сначала проверить тип.
Проверка типа через std::holds_alternative
std::holds_alternative<T> возвращает true, если внутри variant сейчас хранится тип T.
#include <iostream>
#include <string>
#include <variant>
int main() {
std::variant<int, std::string, bool> setting = true;
if (std::holds_alternative<bool>(setting)) {
std::cout << "Boolean: " << std::get<bool>(setting) << std::endl;
}
setting = std::string("admin");
if (std::holds_alternative<std::string>(setting)) {
std::cout << "String: " << std::get<std::string>(setting) << std::endl;
}
}
Это понятный вариант для новичков: проверили тип — получили значение.
std::get_if: получение без исключений
Если вы не хотите работать с исключениями, используйте std::get_if. Он возвращает указатель на значение или nullptr, если тип не совпал.
#include <iostream>
#include <string>
#include <variant>
int main() {
std::variant<int, std::string> result = 404;
if (int* code = std::get_if<int>(&result)) {
std::cout << "Код ошибки: " << *code << std::endl;
}
if (std::string* text = std::get_if<std::string>(&result)) {
std::cout << "Текст: " << *text << std::endl;
} else {
std::cout << "Это не строка" << std::endl;
}
}
std::get_if часто удобен в прикладном коде: нет исключений, логика проверки явно видна.
std::visit: правильная обработка всех вариантов
Самый мощный способ работать с std::variant — std::visit. Он вызывает функцию для текущего значения, независимо от того, какого оно типа.
#include <iostream>
#include <string>
#include <variant>
int main() {
std::variant<int, std::string, double> value = std::string("price");
std::visit([](const auto& item) {
std::cout << "Значение: " << item << std::endl;
}, value);
}
Здесь лямбда принимает const auto&, поэтому подходит для любого типа внутри variant. Это удобно, когда для всех типов можно выполнить одно и то же действие, например вывести значение.
Практический пример: результат операции
Частая задача: функция должна вернуть либо успешный результат, либо ошибку. Для этого можно использовать std::variant.
#include <iostream>
#include <string>
#include <variant>
struct Error {
std::string message;
};
using ParseResult = std::variant<int, Error>;
ParseResult parseNumber(const std::string& text) {
try {
return std::stoi(text);
} catch (...) {
return Error{"Не удалось преобразовать строку в число"};
}
}
int main() {
ParseResult result = parseNumber("123abc");
if (int* number = std::get_if<int>(&result)) {
std::cout << "Число: " << *number << std::endl;
} else if (Error* error = std::get_if<Error>(&result)) {
std::cout << "Ошибка: " << error->message << std::endl;
}
}
В этом примере тип результата явно показывает: функция может вернуть либо int, либо Error. Это читается лучше, чем магические значения вроде -1 или пустой строки.
std::variant или union?
union тоже позволяет хранить разные типы в одной области памяти, но он гораздо менее безопасен. Программист сам должен помнить, какой тип сейчас активен. Ошибка легко приводит к неопределённому поведению.
std::variant хранит информацию об активном типе и предоставляет безопасные инструменты: std::get, std::get_if, std::holds_alternative, std::visit. Поэтому для обычного прикладного кода почти всегда лучше выбирать std::variant.
Типичные ошибки новичков
- Запрашивать не тот тип через std::get. Если не уверены в типе, используйте
std::holds_alternativeилиstd::get_if. - Добавлять слишком много типов. Если в
variantуже 10-15 вариантов, возможно, стоит пересмотреть архитектуру. - Использовать variant вместо обычной структуры. Если объект всегда содержит несколько полей одновременно, нужен
struct, а неvariant. - Путать variant и optional.
std::optional<T>— это «значение есть или нет», аstd::variant<A, B, C>— «значение одного из нескольких типов».
Рекомендации по использованию
- Давайте
variantосмысленное имя черезusing: например,using ConfigValue = std::variant<int, std::string, bool>;. - Для простого чтения используйте
std::get_if— он безопасен и понятен. - Для общей обработки всех типов изучите
std::visit. - Не используйте
variantкак способ спрятать плохую модель данных.
Если вы хотите уверенно разобраться не только с std::variant, но и с современным C++ в целом — от базового синтаксиса до продвинутых приёмов, обратите внимание на курс «Программирование на C++ с Нуля до Гуру». Он хорошо подойдёт тем, кто хочет учиться системно, а не собирать знания по разрозненным статьям.
Вывод
std::variant в C++17 — практичный инструмент для ситуаций, когда значение может иметь один из нескольких известных типов. Он безопаснее union, выразительнее набора флагов и часто проще и понятнее наследования. Начните с std::get_if и std::holds_alternative, а затем переходите к std::visit — так вы быстро научитесь применять std::variant в реальных программах.
-
Создано 04.09.2026 17:00:44
-
Михаил Русаков

Комментарии (0):
Для добавления комментариев надо войти в систему.
Если Вы ещё не зарегистрированы на сайте, то сначала зарегистрируйтесь.