Введение
Долгое время Boost был фактическим «полигоном» для будущих возможностей стандартной библиотеки C++ — многие классы, такие как optional, variant, any, filesystem, появились в Boost задолго до их стандартизации. С приходом C++17 и C++20 значительная часть этого функционала вошла в стандартную библиотеку как std::optional, std::variant, std::any, std::filesystem. В статье разберём, остаётся ли смысл использовать Boost-версии этих компонентов в новых Qt-проектах, ориентированных на современный стандарт.
Концепция
std::optional и boost::optional решают одну и ту же задачу — представление значения, которое может быть отсутствующим, без использования указателей или специальных «магических» значений. Для нового кода, ориентированного на C++17 и новее, std::optional предпочтительнее: он часть стандарта, лучше известен сообществу, и не добавляет зависимость от Boost. То же касается std::variant против boost::variant и std::any против boost::any.
Boost-версии всё ещё оправданы в нескольких случаях: (1) проект должен поддерживать компиляторы или стандарты старше C++17; (2) нужна функциональность, которой нет в стандартной версии (например, boost::variant2 с иной моделью исключений, или boost::optional с поддержкой ссылочных типов, которой нет в std::optional); (3) интеграция со сторонним кодом, уже завязанным на Boost-типы.
Пример кода: Boost.Optional, Boost.Variant и
#include <optional>
#include <variant>
#include <iostream>
#include <string>
// std::optional — стандартная альтернатива boost::optional с C++17
std::optional<int> parseNumber(const std::string &text)
{
try {
return std::stoi(text);
} catch (const std::exception &) {
return std::nullopt;
}
}
// std::variant — стандартная альтернатива boost::variant с C++17
using Shape = std::variant<int /* радиус круга */, std::pair<int, int> /* стороны прямоугольника */>;
double computeArea(const Shape &shape)
{
if (std::holds_alternative<int>(shape)) {
int radius = std::get<int>(shape);
return 3.14159 * radius * radius;
} else {
auto [width, height] = std::get<std::pair<int, int>>(shape);
return width * height;
}
}
int main()
{
auto result = parseNumber("42");
if (result.has_value()) {
std::cout << "Распознано число: " << *result << std::endl;
}
auto invalidResult = parseNumber("not_a_number");
std::cout << "Результат разбора некорректной строки: "
<< invalidResult.value_or(-1) << std::endl;
Shape circle = 5;
Shape rectangle = std::make_pair(4, 6);
std::cout << "Площадь круга: " << computeArea(circle) << std::endl;
std::cout << "Площадь прямоугольника: " << computeArea(rectangle) << std::endl;
return 0;
}
Пояснения к коду
parseNumber демонстрирует типичное применение std::optional — функция, которая может не вернуть результат (некорректный ввод), без выброса исключения наружу и без использования специального «магического» значения вроде -1, которое могло бы быть валидным результатом в другом контексте. value_or() даёт удобный способ подставить значение по умолчанию при отсутствии результата.
Shape на основе std::variant показывает типобезопасную альтернативу классическому union или иерархии классов с виртуальными методами для простых случаев «это один из нескольких заранее известных типов» — std::holds_alternative и std::get дают безопасный доступ к текущему хранимому типу.
Подводные камни
- Слепой перенос старого кода с Boost на std без проверки отличий в API.
std::optionalне поддерживает ссылочные типы (std::optional<T&>не специализирован стандартно так, какboost::optional<T&>), и некоторые тонкости поведения при копировании/перемещении различаются — механическая заменаboost::наstd::не всегда компилируется без изменений. - Версия компилятора/стандарта проекта может быть зафиксирована на C++11/14 по историческим причинам (старый embedded toolchain, устаревшие зависимости) — в таких случаях
std::optional/std::variantпросто недоступны, и Boost остаётся единственным вариантом без серьёзного рефакторинга toolchain. std::variantпри пустом/невалидном состоянии (после исключения при перемещении) уходит вstd::monostate/valueless_by_exception — поведение, отличающееся отboost::variant, которое в более старых версиях гарантировало «никогда не быть пустым» за счёт дополнительных накладных расходов на резервное хранение.- Сохранение зависимости от Boost только из инерции, когда весь функционал, который реально используется, уже покрывается стандартной библиотекой — лишняя зависимость увеличивает время сборки и поверхность для уязвимостей без реальной выгоды.