Klasa std::optional¶
- Nagłówek:
<optional>
Obiekt typu std::optional
opcjonalnie przechowuje wartość określonego typu - jest pusty lub posiada określoną wartość.
W rezultacie nie ma potrzeby korzystać ze specjalnych znaczników pustej wartości (np. NULL, -1, itp.)
Tag pomocniczy - std::nullopt¶
Klasa std::optional
wykorzystuje stałą std::nullopt
typu std::nullopt_t
jako specjalny znacznik oznaczający brak wartości dla obiektu.
inline constexpr nullopt_t nullopt{ /*unspecified*/ };
Konstruktory¶
Obiekt std::optional
może zostać skonstruowany:
w stanie be wartości:
std::optional<std::string> o1; std::optional<double> o2 = std::nullopt;
z określoną wartością
std::optional<std::string> o3 = "text"; std::optional o4{42}; // deduces optional<int>
in-place na podstawie listy argumentów - bez konieczności tworzenia obiektu tymczasowego
std::optional<std::complex<double>> o5{std::in_place, 3.0, 4.0}; // initialize set with lambda as sorting criterion: auto sc = [] (int x, int y) { return std::abs(x) < std::abs(y); }; std::optional<std::set<int, decltype(sc)>> o6{std::in_place, {4, 8, -7, -2, 0, 5}, sc};
przy pomocy funkcji pomocniczej
std::make_optional()
auto o7 = std::make_optional(3.0); // optional<double>
Sprawdzenie stanu¶
Aby sprawdzić, czy obiekt opcjonalny przychowuje wartość możemy użyć:
- metody
has_value()
- przeciążonej funkcji
operator bool
std::optional o{42};
assert(o.has_value() == true);
if (o) // has value
{
//...
}
if (!o) // is empty
{
//...
}
Dostęp do przechowywanej wartości¶
Unsafe¶
Dostęp do przechowywanej wartości zapewniony jest poprzez przeciążenie operatorów dereferencji *
oraz *->
:
*opt_str = "other";
assert(opt_str.value() == "other");
assert(opt_str->length() == 5);
Ostrzeżenie
Użycie tych operatorów w sytuacji, gdy obiekt jest pusty (nie przechowuje wartości) skutkuje undefined behavior
Safe¶
Bezpieczny dostęp do przechowywanej wartości może być zrealizowany poprzez metody:
-
const T &
value
()¶ zwraca wartość. Jeśli jej nie ma rzuca wyjątkiem
std::bad_optional_access
std::optional<std::string> opt_str;
try
{
string str = opt_str.value();
}
catch(const std::bad_optional_access& e)
{
//...
}
-
template<typename
U
>
Tvalue_or
(U &&default_value)¶ zwraca wartość lub jeśli jej nie ma, podaną jako argument wartość domyślną
#include <optional>
#include <iostream>
#include <cstdlib>
std::optional<const char*> maybe_getenv(const char* n)
{
if(const char* x = std::getenv(n))
return x;
else
return {};
}
//...
std::cout << maybe_getenv("MYPWD").value_or("(none)") << '\n';
Resetowanie stanu¶
Usunięcie wartości realizowane jest za pomocą metody reset()
.
Semantyka przenoszenia¶
Klasa std::optional
wspiera semantykę przenoszenia:
std::optional<std::string> os;
std::string text = "text";
os = std::move(text); // OK - string object is moved to optional
std::string destination = std::move(*os);
Ostatnia instrukcja w powyższym przykładzie pozostawia obiekt std::optional
z wartością (os.has_value() == true
), ale w nieokreślonym stanie.
Specjalne przypadki¶
W przypadku zmiennych typu std::optional
przechowywanie w nich wartości typu bool
i wskaźników może mieć zaskakujące efekty.
std::optional<bool>¶
std::optional<bool> o{false};
if (!o) // yields false - o has value, which is false
{
//...
}
if (o == false) // yields true
{
}
std::optional<T*>¶
std::optional<double*> o{nullptr};
if (!o) // yields false - o has value
{
//...
}
if (o == nullptr) // yields true
{
//...
}
Case Study¶
Opcjonalne składowe klasy¶
class Person
{
std::string first_name_;
std::optional<std::string> middle_name_;
std::string last_name_;
public:
Person(std::string fn, std::optional<std::string> mn, std::string ln)
: first_name_{std::move(fn)}, middle_name_{std::move(mn)}, last_name_{std::move(ln)}
{}
std::string full_name() const
{
return first_name_ + " " + ( middle_name_ ? *middle_name_ + " " : "") + last_name_;
}
};
//...
Person p1{"Jan", "Maria", "Kowalski"};
assert(p1.full_name() == "Jan Maria Kowalski");
Person p2{"Jan", std::nullopt, "Kowalski"};
assert(p2.full_name() == "Jan Kowalski");