Wang's blog

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();                                  // 删除包含的值