C++17 详解 17 — std::optional
本文为 《C++17 in detail》 一书的中文渣中渣译文,不足之处还望指正。
翻译太特么累人了……剩余部分还是只做摘要翻译吧。
6. std::optional
std::optional 可表达可空类型。
头文件:
1 |
std::optional 是一种值类型,即执行深拷贝。它不会引入额外的动态内存分配。
用例:
1 | // UI class... |
6.1 创建 std::optional
- 初始化为空:
1 | std::optional<int> oEmpty; |
- 直接赋值
1 | std::optional<int> oInt(10); |
- std::make_optional
1 | auto oDouble = std::make_optional(3.0); |
- std::in_place
1 | std::optional<std::complex<double>> o7{std::in_place, 3.0, 4.0}; |
- 从其它 optional 拷贝
1 | auto oIntCopy = oInt; |
关于 std::in_place
译注:
in place 即原地、就地的意思,指通过参数列表直接调用类型的构造函数进行构造。同 C++11 之后许多容器新增的 emplace 一个作用。
std::in_place 是 std::in_place_t 类型的内置常量。
- 提升性能
如果没有这个参数,当你写下:
1 | std::optional<std::string> ostr{std::string{"Hello World"}}; |
时,会匹配到 optional 的移动构造函数。即:
- 先通过 string 移动构造出临时 optional 对象;
- 再从临时 optional 对象转移构造 ostr 对象。
有了 std::in_place,只需一步:
- 直接将 string 参数传递给 ostr 构造函数。
- 默认构造
有这样一个类:
1 | class UserName |
要构造一个默认值的 UserName 对象该怎么做?
1 | std::optional<UserName> u0; // empty optional |
这样创建的是空的 optional;
1 | std::optional<UserName> u2{UserName()}; |
这样同 1 中描述的情况一样,会产生临时对象,不算默认构造。
正确做法是这样:
1 | std::optional<UserName> opt{std::in_place}; |
- 使 optional 可存储不可拷贝/移动类型
比如 std::mutex,如果没有 std::in_place,optional<mutex>
就不成立。
- std::make_optional 跟 std::in_place 一样作用
1 | auto opt = std::make_optional<UserName>(); |
6.3 访问存储值
- operator* 和 operator-> ——如果 std::optional 为空行为未定义
1 | std::optional<int> oint = 10; |
- value() ——如果空抛出 std::bad_optional_access 异常
1 | std::optional<std::string> ostr("hello"); |
- value_or(defaultVal) ——如果空返回 defaultVal
1 | std::optional<double> odouble; // empty |
6.4 其它操作
修改值
- emplace:原地构造存储值。如果当前不为空,当前值会被销毁。
- reset:销毁当前值,并置为空。
- swap:交换两个 optional 的值。
比较
- 两个空 optional 相等
- 空 optional 总是小于非空 optional
- 两个非空 optional 以其值类型的比较操作符进行比较
1 | int main() |
6.6 慎用 std::optional 和 optional<T*>
因为 bool 和 T* 本身就已代表一种二态值,强行将其跟 std::optional 组合成三态不够自然,会增加开发人员心智负担。