std::optional、std::variant与std::any
Published on
C++17中提出了以下三种数据类型用于更加安全、方便地处理可变数据。
std::optional
类模板std::optional管理一个可选的容纳值,该值既可以存在也可以不存在。虽然直接使用指针就可以实现相同的功能,但是容易引发内存泄漏或二次释放等问题。其它实现方式(如使用std::pair<T,bool>
)语法复杂且效率低下,而std::optional可以简单、高效地处理此问题。
// 初始化为不包含值
std::optional<int> opt1;
std::optional<int> opt2(std::nullopt);
std::optional<int> opt3 = std::nullopt;
// 初始化为包含值
std::optional<int> op4(1);
std::optional<int> op5 = 2;
std::optional<int> op6 = std::make_optional<int>(3);
// 判断是否包含值
if (opt1.has_value())
{
// 可使用*或->操作符访问包含的值,但是需要自行判断是否包含值
std::cout << "opt1: " << *opt1 << '\n';
}
// 实现了bool运算符,表示是否包含值
if (opt1)
{
std::cout << "opt1: " << *opt1 << '\n';
}
// 使用value成员函数获取包含的值,如果不包含值则抛出异常
try
{
int n = opt1.value();
}
catch (const std::exception &e)
{
std::cout << e.what() << '\n';
}
// 使用value_or成员函数获取包含的值,如不包含则返回默认值
int n = opt1.value_or(5);
// 赋予新值
opt1 = 6;
opt1.emplace(7);
// 删除包含的值
opt1.reset();
std::variant
类模板std::variant表示一个类型安全的union,它在任意时刻保有其可选类型之一的值。与union不同的是,std::variant中当前包含值的类型是可以获取的,因此更加安全。
std::variant<int, float> v;
v = 1; // 使v包含int
v.emplace<int>(2); // 使v包含int
size_t index = v.index(); // 获取目前包含类型的索引
if (std::holds_alternative<int>(v)) // 判断当前是否包含指定类型
{
int i = std::get<int>(v); // 通过类型获取包含的值
int j = std::get<0>(v); // 通过索引获取包含的值
}
try
{
std::get<float>(v); // 获取目前不包含的类型时,抛出异常
}
catch (const std::bad_variant_access &)
{
}
std::get<double>(v); // 错误,不能包含double类型
std::get<3>(v); // 错误,索引超出范围
std::any
类std::any是一个类型安全容器,可以放置任何可拷贝构造类型的单个值。使用void*可以实现相同的功能,但是使用指针可能引发各种问题,而使用std::any更加安全。
std::any a; // 不包含任何值
std::any b = 1; // 包含int
auto c = std::make_any<float>(2.0f); // 包含float
std::any d(3.0); // 包含double
if (a.has_value()) // 判断是否包含值
{
std::cout << a.type().name() << '\n'; // 获取包含值的类型名称
}
try
{
int i = std::any_cast<int>(a); // 获取包含的值,如果类型错误则抛出异常
int *p = std::any_cast<int>(&a); // 获取包含值的指针,如果类型错误则抛出异常
}
catch (const std::bad_any_cast &e)
{
std::cout << e.what() << '\n';
}
a = 4.0; // 更改包含的值
a.emplace<float>(5.0f); // 更改包含的值
a.reset(); // 删除包含的值