C++17 详解 17 — std::optional

本文为 《C++17 in detail》 一书的中文渣中渣译文,不足之处还望指正。

翻译太特么累人了……剩余部分还是只做摘要翻译吧。

6. std::optional

std::optional 可表达可空类型。

头文件:

1
#include <optional>

std::optional 是一种值类型,即执行深拷贝。它不会引入额外的动态内存分配。

用例:

1
2
3
4
5
6
7
8
9
10
11
12
13
// UI class...
std::optional<std::string> UI::FindUserNick()
{
if (IsNickAvailable())
return mStrNickName; // return a string

return std::nullopt; // same as return { };
}

// use:
std::optional<std::string> UserNick = UI->FindUserNick();
if (UserNick)
Show(*UserNick);

6.1 创建 std::optional

  1. 初始化为空:
1
2
std::optional<int> oEmpty;
std::optional<float> oFloat = std::nullopt;
  1. 直接赋值
1
2
std::optional<int> oInt(10);
std::optional oIntDeduced(10); // 自动推断
  1. std::make_optional
1
2
auto oDouble = std::make_optional(3.0);
auto oComplex = std::make_optional<std::complex<double>>(3.0, 4.0);
  1. std::in_place
1
2
3
4
std::optional<std::complex<double>> o7{std::in_place, 3.0, 4.0};

// 会通过 {1, 2, 3} 对 vector 进行直接初始化
std::optional<std::vector<int>> oVec(std::in_place, {1, 2, 3});
  1. 从其它 optional 拷贝
1
auto oIntCopy = oInt;

关于 std::in_place

译注:

in place 即原地、就地的意思,指通过参数列表直接调用类型的构造函数进行构造。同 C++11 之后许多容器新增的 emplace 一个作用。

std::in_place 是 std::in_place_t 类型的内置常量。

  1. 提升性能

如果没有这个参数,当你写下:

1
std::optional<std::string> ostr{std::string{"Hello World"}};

时,会匹配到 optional 的移动构造函数。即:

  • 先通过 string 移动构造出临时 optional 对象;
  • 再从临时 optional 对象转移构造 ostr 对象。

有了 std::in_place,只需一步:

  • 直接将 string 参数传递给 ostr 构造函数。
  1. 默认构造

有这样一个类:

1
2
3
4
5
6
7
8
class UserName
{
public:
UserName() : mName("Default")
{
}
// ...
};

要构造一个默认值的 UserName 对象该怎么做?

1
2
std::optional<UserName> u0; // empty optional
std::optional<UserName> u1{}; // also empty

这样创建的是空的 optional;

1
std::optional<UserName> u2{UserName()};

这样同 1 中描述的情况一样,会产生临时对象,不算默认构造。

正确做法是这样:

1
std::optional<UserName> opt{std::in_place};
  1. 使 optional 可存储不可拷贝/移动类型

比如 std::mutex,如果没有 std::in_place,optional<mutex> 就不成立。

  1. std::make_optional 跟 std::in_place 一样作用
1
2
3
4
5
auto opt = std::make_optional<UserName>();
auto opt = std::make_optional<Point>(0, 0);
// 跟下边两句等价
std::optional<UserName> opt{std::in_place};
std::optional<Point> opt{std::in_place_t, 0, 0};

6.3 访问存储值

  1. operator* 和 operator-> ——如果 std::optional 为空行为未定义
1
2
std::optional<int> oint = 10;
std::cout<< "oint " << *oint << '\n';
  1. value() ——如果空抛出 std::bad_optional_access 异常
1
2
3
4
5
6
7
8
9
std::optional<std::string> ostr("hello");
try
{
std::cout << "ostr " << ostr.value() << '\n';
}
catch (const std::bad_optional_access& e)
{
std::cout << e.what() << '\n';
}
  1. value_or(defaultVal) ——如果空返回 defaultVal
1
2
std::optional<double> odouble; // empty
std::cout<< "odouble " << odouble.value_or(10.0) << '\n';

6.4 其它操作

修改值

  • emplace:原地构造存储值。如果当前不为空,当前值会被销毁。
  • reset:销毁当前值,并置为空。
  • swap:交换两个 optional 的值。

比较

  • 两个空 optional 相等
  • 空 optional 总是小于非空 optional
  • 两个非空 optional 以其值类型的比较操作符进行比较
1
2
3
4
5
6
7
8
9
10
11
12
13
int main()
{
std::optional<int> oEmpty;
std::optional<int> oTwo(2);
std::optional<int> oTen(10);

std::cout << std::boolalpha;
std::cout << (oTen > oTwo) << '\n'; // true
std::cout << (oTen < oTwo) << '\n'; // false
std::cout << (oEmpty < oTwo) << '\n'; // true
std::cout << (oEmpty == std::nullopt) << '\n'; // true
std::cout << (oTen == 10) << '\n'; // true
}

6.6 慎用 std::optional 和 optional<T*>

因为 bool 和 T* 本身就已代表一种二态值,强行将其跟 std::optional 组合成三态不够自然,会增加开发人员心智负担。

评论