std::optional

  1. 背景

    在编程时,我们经常会遇到可能会返回/传递/使用一个确定类型对象的场景。也就是说,这个对象可能有一个确定类型的值也可能没有任何值。因此,我们需要一种方法来模拟类似指针的语义:指针可以通过 nullptr来表示没有值。解决方法是定义该对象的同时再定义一个附加的 bool类型的值作为标志来表示该对象是否有值。std::optional<>提供了一种类型安全的方式来实现这种对象。
  2. 占用内存大小

    可选对象所需的内存等于内含对象的大小加上一个 bool类型的大小。因此,可选对象一般比内含对象大一个字节(可能还要加上内存对齐的空间开销)。可选对象不需要分配堆内存,并且对齐方式和内含对象相同。
#include <iostream>
#include <optional> // 定义一个没有默认构造函数的类
class MyClass {
public:
explicit MyClass(int value) : data(value) {}
~MyClass() {} int getData() const {
return data;
} private:
int data;
}; // 输出 std::optional 是否包含值
void check_optional_value(std::optional<MyClass>& opt) {
if (opt) {
std::cout << "Value present: " << opt->getData() << std::endl;
} else {
std::cout << "No value present." << std::endl;
}
} int main() {
// 创建一个没有值的 std::optional<MyClass>
std::optional<MyClass> opt1;
check_optional_value(opt1); // 创建一个有值的 std::optional<MyClass>
std::optional<MyClass> opt2{MyClass(42)};
check_optional_value(opt2); // 尝试通过 emplace 添加值
opt1.emplace(24);
check_optional_value(opt1); // 尝试通过 operator= 添加值
opt1 = MyClass(56);
check_optional_value(opt1); return 0;
}

输出:

Size of i: 4 bytes

Size of St8optionalIiE: 8 bytes

Size of 7MyClass: 4 bytes

Size of St8optionalI7MyClassE: 8 bytes

然而,可选对象并不是简单的等价于附加了bool标志的内含对象。例如,在没有值的情况下,将不会调用内含对象的构造函数(通过这种方式,没有默认构造函数的内含类型也可以处于有效的默认状态)。

3.语义

和 std::variant<>、std::any一样,可选对象有值语义。也就是说,拷贝操作会被实现为深拷贝:将创建一个新的独立对象,新对象在自己的内存空间内拥有原对象的标记和内含值(如果有的话)的拷贝。拷贝一个无内含值的 std::optional<>的开销很小,但拷贝有内含值的 std::optional<>的开销约等于拷贝内含值的开销。另外,std::optional<>对象也支持 move语义。

4.应用

(1)std::optional<>模拟了一个可以为空的任意类型的实例。它可以被用作成员、参数、返回值等。

下面的示例程序展示了将 std::optional<>用作返回值的一些功能:

#include <optional>
#include <string>
#include <iostream> // 如果可能的话把string转换为int:
std::optional<int> asInt(const std::string& s)
{
try {
return std::stoi(s);
}
catch (...) {
return std::nullopt;
}
} int main()
{
for (auto s : {"42", " 077", "hello", "0x33"}) {
// 尝试把s转换为int,并打印结果:
std::optional<int> oi = asInt(s);
if (oi.has_value()) {
std::cout << "convert '" << s << "' to int: " << oi.value() << "\n";
}
else {
std::cout << "can't convert '" << s << "' to int\n";
}
}
}

(2) 另一个使用 std::optional<>的例子是传递可选的参数和设置可选的数据成员:

#include <optional>
#include <string>
#include <iostream> class Name
{
private:
std::string first;
std::optional<std::string> middle;
std::string last;
public:
Name (std::string f, std::optional<std::string> m, std::string l)
: first{std::move(f)}, middle{std::move(m)}, last{std::move(l)} {
}
friend std::ostream& operator << (std::ostream& strm, const Name& n) {
strm << n.first << ' ';
if (n.middle) {
strm << *n.middle << ' ';
}
return strm << n.last;
}
}; int main()
{
Name n{"Jim", std::nullopt, "Knopf"};
std::cout << n << '\n'; Name m{"Donald", "Ervin", "Knuth"};
std::cout << m << '\n';
}

5.std::optional<>类型和操作

(1)std::optional<>类型标准库在头文件 中以如下方式定义了 std::optional<>类:

namespace std {

template class optional;

}

另外还定义了下面这些类型和对象:

• std::nullopt_t类型的 std::nullopt,作为可选对象无值时候的“值”。

• 从 std::exception派生的 std::bad_optional_access异常类,当无值时候访问值将会抛出该异常。

可选对象还使用了 头文件中定义的 std::in_place对象(类型是 std::in_place_t)来支持用多个参数初始化可选对象(见下文)。

(2)std::optional<>的操作

表std::optional的操作列出了 std::optional<>的所有操作:

#include <iostream>
#include <optional>
#include <variant>
#include <vector>
#include <set>
#include <map>
#include <string>
#include <cmath>
#include <functional>
#include <cassert>
#include <complex> // 使用命名空间简化代码
using namespace std::string_literals; // 示例 1:构造 std::optional
void construct_optional() {
std::optional<int> o1; // 不含有值
assert(!o1.has_value()); std::optional<int> o2(std::nullopt); // 显式表示不含有值
assert(!o2.has_value()); std::optional o3{42}; // 推导出 std::optional<int>
assert(o3.has_value());
assert(*o3 == 42); std::optional o4{"hello"}; // 推导出 std::optional<const char*>
assert(o4.has_value());
assert(*o4 == "hello"); std::optional o5{"hello"s}; // 推导出 std::optional<std::string>
assert(o5.has_value());
assert(*o5 == "hello"); // 用多个参数初始化可选对象
std::optional<std::complex<double>> o6{std::in_place, 3.0, 4.0};
assert(o6.has_value());
assert(o6->real() == 3.0 && o6->imag() == 4.0); // 使用 std::make_optional
auto o13 = std::make_optional(3.0); // std::optional<double>
assert(o13.has_value());
assert(*o13 == 3.0); auto o14 = std::make_optional("hello"); // std::optional<const char*>
assert(o14.has_value());
assert(*o14 == "hello"); auto o15 = std::make_optional<std::complex<double>>(3.0, 4.0);
assert(o15.has_value());
assert(o15->real() == 3.0 && o15->imag() == 4.0);
} // 示例 2:访问值
void access_optional_value() {
std::optional<std::pair<int, std::string>> o{std::make_pair(42, "hello")};
assert(o.has_value());
assert(o->first == 42);
assert(o->second == "hello"); std::optional<std::string> o2{"hello"};
assert(o2.has_value());
assert(*o2 == "hello"); // 当没有值时访问会导致未定义行为
o2 = std::nullopt;
assert(!o2.has_value());
// std::cout << *o2 << std::endl; // 未定义行为
} // 示例 3:使用 value_or
void use_value_or() {
std::optional<std::string> o{"hello"};
std::cout << o.value_or("NO VALUE") << std::endl; // 输出 "hello" o = std::nullopt;
std::cout << o.value_or("NO VALUE") << std::endl; // 输出 "NO VALUE"
} // 示例 4:比较
void compare_optionals() {
std::optional<int> o0;
std::optional<int> o1{42};
assert(o0 == std::nullopt);
assert(!(o0 == 42));
assert(o0 < 42);
assert(!(o0 > 42));
assert(o1 == 42);
assert(o0 < o1);
assert(!(o0 > o1)); std::optional<unsigned> uo;
assert(uo < 0);
assert(uo < -42); std::optional<bool> bo;
assert(bo < false); std::optional<int> o2{42};
std::optional<double> o3{42.0};
assert(o2 == 42);
assert(o3 == 42);
assert(o2 == o3);
} // 示例 5:修改值
void modify_optional_value() {
std::optional<std::complex<double>> o; // 没有值
std::optional<int> ox{77}; // optional<int>,值为77
o = 42; // 值变为 complex(42.0, 0.0)
assert(o.has_value());
assert(o->real() == 42.0 && o->imag() == 0.0); o = std::complex<double>{9.9, 4.4}; // 值变为 complex(9.9, 4.4)
assert(o.has_value());
assert(o->real() == 9.9 && o->imag() == 4.4); o = ox; // OK,因为 int 转换为 complex<double>
assert(o.has_value());
assert(o->real() == 77.0 && o->imag() == 0.0); o = std::nullopt; // o 不再有值
assert(!o.has_value()); o.emplace(5.5, 7.7); // 值变为 complex(5.5, 7.7)
assert(o.has_value());
assert(o->real() == 5.5 && o->imag() == 7.7); o.reset(); // o 不再有值
assert(!o.has_value()); o = std::complex<double>{88.0, 0.0}; // OK:值变为 complex(88.0, 0.0)
assert(o.has_value());
assert(o->real() == 88.0 && o->imag() == 0.0); o = std::complex<double>{1.2, 3.4}; // OK:值变为 complex(1.2, 3.4)
assert(o.has_value());
assert(o->real() == 1.2 && o->imag() == 3.4);
} // 示例 6:使用 lambda 初始化 set
void initialize_set_with_lambda() {
auto sc = [](int x, int y) {
return std::abs(x) < std::abs(y);
}; std::optional<std::set<int, decltype(sc)>> o8{std::in_place,
std::initializer_list<int>{4, 8, -7, -2, 0, 5},
sc};
assert(o8.has_value());
assert(o8->size() == 6);
} int main() {
construct_optional();
access_optional_value();
use_value_or();
compare_optionals();
modify_optional_value();
initialize_set_with_lambda();
return 0;
}

6.注意

(1)value()和 value_or()

value()和 value_or()之间有一个需要考虑的差异:4 value_or()返回值,而 value()返回引用。这意味着如下调用:

std::cout << middle.value_or("");

和:

std::cout << o.value_or("fallback");

都会暗中分配内存,而 value()永远不会。

然而,当在临时对象 (rvalue)上调用 value_or()时,将会移动走内含对象的值并以值返回,而不是调用拷贝函数构造。这是唯一一种能让 value_or()适用于 move-only的类型的方法,因为在左值 (lvalue)上调用的 value_or()的重载版本需要内含对象可以拷贝。

因此,上面例子中效率最高的实现方式是:

std::cout << o ? o‐>c_str() : "fallback";

而不是:

std::cout << o.value_or("fallback");

value_or()是一个能够更清晰地表达意图的接口,但开销可能会更大一点。

(2)bool 类型或原生指针的可选对象

将可选对象用作 bool值时使用比较运算符会有特殊的语义。如果内含类型是 bool或者指针类型的话这可能导致令人迷惑的行为。例如:

std::optional ob{false}; // 值 为false

if (!ob) ... // 返 回false

if (ob == false) ... // 返 回true

std::optional<int*> op{nullptr};

if (!op) ... // 返 回false

if (op == nullptr) ... // 返 回true

C++17新特性探索:拥抱std::optional,让代码更优雅、更安全的更多相关文章

  1. c++17 新特性

    编译环境说明:gcc 8.1 + eclipse +windows 10 eclipse cpp默认支持c++14,做c++17开发时,需要手动进行配置. 1.关键字 1)constexpr c++1 ...

  2. Java 17 新特性:switch的模式匹配(Preview)

    还记得Java 16中的instanceof增强吗? 通过下面这个例子再回忆一下: Map<String, Object> data = new HashMap<>(); da ...

  3. C++17 新特性之 std::optional(上)

    最近在学习 c++ 17 的一些新特性,为了加强记忆和理解,把这些内容作为笔记记录下来,有理解不对的地方请指正,欢迎大家留言交流. 引言 在介绍之前,我们从一个问题出发,C++ 的函数如何返回多个值? ...

  4. C++17新特性optional和string_view

    1. optional的作用 类模板 std::optional 管理一个可选的容纳值,即可以存在也可以不存在的值. 一种常见的 optional 使用情况是一个可能失败的函数的返回值.与其他手段,如 ...

  5. Microsoft Dynamics AX 7 新特性探索 - Demo 部署(Part 1)

    Dynamics AX 7已经发布了一段时间了,我们知道这次微软为我们带来了许多令人激动的新特性.在这个系列里,Reinhard将揭开New Dynamics AX的神秘面纱,和大家一起探索这些新的特 ...

  6. C++11新特性,利用std::chrono精简传统获取系统时间的方法

    一.传统的获取系统时间的方法 传统的C++获取时间的方法须要分平台来定义. 相信百度代码也不少. 我自己写了下,例如以下. const std::string getCurrentSystemTime ...

  7. C++11 & C++14 & C++17新特性

    C++11:C++11包括大量的新特性:包括lambda表达式,类型推导关键字auto.decltype,和模板的大量改进. 新的关键字 auto C++11中引入auto第一种作用是为了自动类型推导 ...

  8. 【Java8新特性】不了解Optional类,简历上别说你懂Java8!!

    写在前面 最近,很多读者出去面试都在Java8上栽了跟头,事后自己分析,确实对Java8的新特性一知半解.然而,却在简历显眼的技能部分写着:熟练掌握Java8的各种新特性,能够迅速使用Java8开发高 ...

  9. 从Java 9 到 Java 17 新特性梳理

    Java 9 新的创建集合的方法  // [1, 2, 3, 4]  List<Integer> integers = List.of(1, 2, 3, 4);  // {1,2,3}   ...

  10. 转:【Java并发编程】之二十二:并发新特性—障碍器CyclicBarrier(含代码)

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/17512983 CyclicBarrier(又叫障碍器)同样是Java5中加入的新特性,使用 ...

随机推荐

  1. Java-JSP既可以指定定义HTML标签,又可以定义java代码

    概念: Java Server Pages:java服务器端页面 可以理解为:一个特殊页面,其中既可以指定定义HTML标签,又可以定义java代码 用于简化书写 原理: JSP本质上就是一个Servl ...

  2. 探索Nuxt.js的useFetch:高效数据获取与处理指南

    title: 探索Nuxt.js的useFetch:高效数据获取与处理指南 date: 2024/7/15 updated: 2024/7/15 author: cmdragon excerpt: 摘 ...

  3. [oeasy]教您玩转python - 0005- 勇闯地下城

     ​ 继续运行 回忆上次内容 上次从1行代码进化到了2行代码 yy p粘贴剪贴板中的内容 将剪贴板中的代码粘贴9999次 9999p 真的实现了万行代码梦 是真·圆梦 没有撒谎的那种 不过圆梦之后多少 ...

  4. 题解:CF1985E Secret Box

    设长宽高分别为 \(a,b,c\). 由题意可轻松的得到以下求方案数公式. \((x-a+1)(y-b+1)(z-c+1)\) 然后根据这个公式模拟即可. AC Code

  5. 如何用 WinDbg 调试Linux上的 .NET程序

    一:背景 1. 讲故事 最新版本 1.2402.24001.0 的WinDbg真的让人很兴奋,可以将自己伪装成 GDB 来和远程的 GDBServer 打通来实现对 Linux 上 .NET程序进行调 ...

  6. 假期小结8XML之LXML

    这桌我初步学习了爬虫相关知识的python库LXML的一些基本用法 以下是我的部分总结 lxml是Python中一个流行的第三方库,用于处理XML和HTML数据.它提供了高效且易于使用的工具,使你能够 ...

  7. 如何使用ventoy安装操作系统

    使用ventoy安装操作系统 vrntoy简介 简单来说,Ventoy是一个制作可启动U盘的开源工具. 有了Ventoy你就无需反复地格式化U盘,你只需要把 ISO/WIM/IMG/VHD(x)/EF ...

  8. 【Spring Data JPA】08 多表关系 Part1 一对多关系操作

    表关系概述: 1.一 对应 一 一对夫妻,一个男人只能有一个老婆,一个女人只能有一个老公,即这种对应关系 2.一 对应 多 [多对一] 一个年级具有多个班级,一个班级具有对应的所属年级,即这种上下层级 ...

  9. 【Layui】14 代码修饰器 CodeDecorator

    文档地址: https://www.layui.com/demo/code.html 基本案例: <pre class="layui-code">//在里面存放任意的代 ...

  10. 自动驾驶开源数据库 —— nuscenes

    地址: https://www.nuscenes.org/