std::optional 是在 C++ 17 中引入到标准库中的,C++ 17 之前的版本可以通过 boost::optional 实现几乎相同的功能。
适用场景
需要表示或处理一个“可能为空”的变量,可能是一个为包含任何元素的容器,可能是一个类型的指针没有指向任何有效的对象实例,再或者是一个对象没有被赋予有效的值。通常处理这类问题意味着写更多的代码来处理这些“特殊”情况,很容易导致代码变得冗余,可读性变差或者容易出错。
针对这个问题,有几种方法:
- 使用魔术值(magic value)或者空指针(null pointers),如0,-1或者一个最大的非负值(例如std::string::npos)等。魔术值限制了可获得的值得范围,而且对于一些类型,没有明显的魔术值,或者无法用常规手段创建魔术值;用空指针表示没有意义的值意味着其他合法的值必须被分配一个地址空间,这是一个代价高昂的操作并且难以实现。
- 如果函数可能出错导致返回结果是无效值,我们会引入boolean或者error code作为函数返回值来表示结果是否有意义。但是这种方法会使函数接口变得隐晦,因为接口的使用者可能并不会检查函数返回值而直接使用结果。
- 抛出异常。这样就必须引入try-catch代码块来处理这些异常,使得代码变得冗余,可读性变差。
std::optional为解决这类问题提供了简单的解决方案。optional可以看作是T类型变脸与一个布尔值的打包(wrapper class)。 其中的布尔值用来表示T是否为“空”。
std:optional
std:optional是一个sum type(和类型,例如一个union包含两个类型,一个bool类型和一个uint8_t类型,那么这个union一共会有2+2**8= 258种值,称之为和类型,因为它们的类型数量是用各个类型的类型数量累加求得的。如果换成struct,那么这里的类型数量就是2*2^8=512种),它是类型T 的所有值和一个单独的“什么都没有”的状态的和(它的类型是std::nullopt_t,并且它有一个值std::nullopt)。
简单使用
初始化
// 初始化为空
std::optional<int> emptyInt;
std::optional<double> emptyDouble = std::nullopt;
// 有效值初始化
std::optional<int> intOpt{10};
std::optional intOptDeduced{10.0}; // auto deduced
// 使用make_optional
auto doubleOpt = std::make_optional(10.0);
auto complexOpt = std::make_optional<std::complex<double>>(3.0, 4.0);
// 使用in_place
std::optional<std::complex<double>> complexOpt{std::in_place, 3.0, 4.0};
std::optional<std::vector<int>> vectorOpt{std::in_place, {1, 2, 3}};
// 使用其它optional对象构造
auto optCopied = vectorOpt;
其中,in_place / make_optional来对optional对象进行“原地”构造有几个原因:
- optional存储的对象需要使用默认构造函数进行构造
- optional内部存储对象不支持拷贝和移动(non-copyable,non-movable)
- 提高构造函数有多个参数的类型对象的构造效率
has_value()告诉我们是否有一个值,value()则返回这个值。
如果没有值并且调用了value(),会抛出一个类型为std::bad_optional_access的异常。
可以使用value_or(U&& default)来得到值,如果std::optional为空,则得到default。
reset()清除std::optional包含的对象,让它为空。
类似于std::make_unique和std::make_shared,std::make_optional可以在一个新的std::optional内构造T。
class tStudent
{
public:
explicit tStudent(std::string str)
: m_name(str)
{}
~tStudent() = default;
};
// 构造空的optional
std::optional<tStudent> optStudent;
// 构造名字为“Bob”的tStudent对象存储在optional对象中
optStudent.emplace("Bob");
// 相当于
// optStudent = tStudent{"Bob"};
// "Bob"对象析构,构造"Steve"
optStudent.emplace("Steve")
// "Steve"对象析构
optStudent.reset();
#include <iostream>
#include <optional>
using namespace std;
struct Out {
string out1;
string out2;
};
optional<Out> func(const string& in) {
Out o;
if (in.size() == 0)
return nullopt;
o.out1 = "hello";
o.out2 = "world";
return o;
}
int main() {
if (auto ret = func("hello_world"); ret.has_value()) {
cout << ret->out1 << endl;
cout << ret->out2 << endl;
}
return 0;
}