Введение
Долгое время C++-разработчики выражали «значение может отсутствовать» через указатели и nullptr, через специальные «магические» значения (-1, 0, пустая строка) или через пару «значение + флаг успешности». Все эти подходы работают, но они хрупкие: указатель может означать и «значения нет», и «объект существует где-то на куче», и легко забыть проверку на nullptr. C++17 принёс std::optional<T> — типобезопасную обёртку, которая явно говорит: «здесь либо есть значение типа T, либо его нет».
Концепция
std::optional<T> — это объект, который либо содержит значение типа T (хранится по значению, без выделения памяти на куче), либо находится в состоянии «пусто» (std::nullopt). В отличие от указателя, optional не предполагает владения через сырую память и не требует new/delete. Семантически он ближе к Maybe/Option из функциональных языков.
Главные операции:
has_value()или приведение кbool— есть ли значение;value()— получить значение (бросаетstd::bad_optional_access, если значения нет);operator*иoperator->— доступ без проверки (UB, если значения нет);value_or(default)— получить значение или запасной вариант.
Пример кода
#include <optional>
#include <iostream>
#include <string>
#include <map>
struct User {
std::string name;
int age;
};
// Старый подход: указатель + nullptr
const User* findUserOld(const std::map<int, User>& db, int id) {
auto it = db.find(id);
if (it == db.end()) {
return nullptr;
}
return &it->second;
}
// Новый подход: std::optional
std::optional<User> findUser(const std::map<int, User>& db, int id) {
auto it = db.find(id);
if (it == db.end()) {
return std::nullopt;
}
return it->second; // копия найденного значения
}
std::optional<int> parseInt(const std::string& s) {
try {
size_t pos = 0;
int value = std::stoi(s, &pos);
if (pos != s.size()) {
return std::nullopt; // не вся строка распознана
}
return value;
} catch (...) {
return std::nullopt;
}
}
int main() {
std::map<int, User> db = {
{1, {"Alice", 30}},
{2, {"Bob", 25}}
};
if (auto user = findUser(db, 1)) {
std::cout << "Found: " << user->name << ", age " << user->age << "n";
} else {
std::cout << "Not foundn";
}
// value_or — удобная альтернатива тернарному оператору
auto missing = findUser(db, 99);
std::cout << "Name or default: "
<< (missing ? missing->name : "Unknown") << "n";
// Парсинг с понятным результатом
auto n1 = parseInt("42");
auto n2 = parseInt("abc");
std::cout << "n1 = " << n1.value_or(-1) << "n";
std::cout << "n2 = " << n2.value_or(-1) << "n";
return 0;
}
Пояснения к коду
В findUserOld мы возвращаем сырой указатель на элемент map, и вызывающая сторона обязана не забыть проверить его на nullptr. Кроме того, такой указатель может «протухнуть», если контейнер изменится после вызова. findUser с std::optional<User> явно сигнализирует через тип, что значение может отсутствовать, и при этом возвращает копию данных, а не ссылку на внутреннее хранилище контейнера — то есть результат безопасен в использовании даже после изменения db.
Функция parseInt — классический пример: вместо исключений или флага-указателя на ошибку мы возвращаем optional<int>, и вызывающий код через value_or получает запасное значение без ветвлений try/catch.
Подводные камни и советы
1. optional — это не указатель: при копировании optional<T> копируется и T. Если T — тяжёлый объект, рассмотрите std::optional<std::reference_wrapper<T>> или передачу по указателю, если избыточное копирование критично. 2. operator* и operator-> не проверяют наличие значения — обращение к пустому optional это undefined behavior. Используйте value() (бросает исключение) там, где безопасность важнее производительности, либо явно проверяйте has_value(). 3. Не путайте optional<bool> с bool — тройная логика (нет значения / false / true) иногда неочевидна для читающего код. 4. optional не подходит для отличия «ошибка А» от «ошибки Б» — если нужно несколько причин отсутствия значения, используйте std::expected (C++23) или std::variant<T, Error>. 5. Используйте optional в сигнатурах функций, где раньше был bool tryGet(T& out) или пары (value, found) — это значительно улучшает читаемость вызывающего кода.
================================================================================