std::optional
std::optional<T>
代表一个可能存在的T值,实际上是一种Sum Type。常用于可能失败的函数的返回值中,比如工厂函数。在C++17之前,往往使用T*
作为返回值,如果为nullptr
则代表函数失败,否则T*
指向了真正的返回值。但是这种写法模糊了所有权,函数的调用方无法确定是否应该接管T*
的内存管理,而且T*
可能为空的假设,如果忘记检查则会有SegFault的风险。
使用指针和optional的代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// c++17 更安全和直观
std::optional<int> func(bool param)
{
if(param)
{
return 1;
}
return nullopt;
}
int* func2(bool param)
{
int p = 1;
int* ptr = &p;
if(param)
{
return ptr;
}
return nullptr;
}
auto f = func(false);
if(f.has_value() )
cout << f.value() << endl;
auto f2 = func2(false);
if(f2)
cout << *f2 << endl;
如果换成unique_ptr代替optional,后果是多一次堆内存分配。另外如果用了unique_ptr
做类成员,这个类就不能被拷贝了,optional
没有这个问题。二者的本质区别: unique_ptr
是引用语义,optional
是值语义
variant
std::variant
是 C++17 引入的一种类型安全的联合体,用来存储多个可能类型中的一种值,且保证使用时的类型安全。相比于传统的 union
, std::variant
不仅能够存储不同类型的值,还能自动管理复杂类型的构造与析构。
使用 std::variant
可以定义一个变量,该变量可以持有多种不同类型的值,但一次只能存储一种。std::variant
提供了一种类型安全的方式来处理多种类型,可以用在函数参数中以接受这些类型。
构造和赋值:您可以直接使用各种支持的类型初始化 std::variant
, 并在需要时将其传递给函数。
1 | std::variant<int, float, std::string> v; // v 可以是 int、float 或 std::string |
std::variant
可以作为函数参数,接受多种类型的值,这些类型是在定义 std::variant
时指定的
std::variant
主要用于以下场景:
- 多类型返回值: 一个函数可能返回不同类型的值,例如成功返回数据,失败返回错误信息
- 替代 union:
std::variant
是 union 的现代、安全替代品,支持更多的类型和安全检查 std::variant
可以表示有限状态机的状态,每个状态用不同的类型表示
类型安全: 使用 std::visit
可确保访问的值是有效的,避免了类型错误1
2
3std::visit([](auto&& arg) {
std::cout << "use visit to print value: " << arg << std::endl; // 打印不同类型的值
}, v);
std::any
std::any是一个可以存储任何可拷贝类型的容器,C语言中通常使用void*
实现类似的功能,与void*
相比,std::any具有两点优势:
std::any更安全:在类型T被转换成void*
时,T的类型信息就已经丢失了,在转换回具体类型时程序无法判断当前的void*
的类型是否真的是T,容易带来安全隐患。而std::any
会存储类型信息,std::any_cast
是一个安全的类型转换。std::any
管理了对象的生命周期,在std::any析构时,会将存储的对象析构,而void*
则需要手动管理内存。std::any
应当很少是程序员的第一选择,在已知类型的情况下,std::optional
, std::variant
和继承都是比它更高效、更合理的选择。只有当对类型完全未知的情况下,才应当使用std::any,比如动态类型文本的解析或者业务逻辑的中间层信息传递。