昨天在写《深入探讨C++的高级反射机制(2):写个能用的反射库》的时候,正好遇到动态反射需要的类型擦除技术。所谓的类型擦除,就是在两个模块之间的接口层没有任何类型信息,实现两个模块之间安全的通信。可以理解为:
为了实现这个功能,于是用到了std::any这个工具。考虑到许多开发者分不清std::variant和std::any之间的区别,于是萌发了写一篇文章系统性介绍一下他们的想法。
传统上,C++17标准为C++的类型系统和容器库带来了重要的补充和改进,std::optional, std::any, 和 std::variant这三种类型被统称为“C++17容器三剑客”,本文将在C++11引入在C++17获得的增强的std::tuple也纳入介绍,系统性地揭示C++体系中这类容器的完整面貌。
1. std::optional —— 语义明确的可选值
std::optional是一个模板类型,它提供了一种表示“可能没有值”的方式。在C++17之前,程序员通常会使用指针、特殊值或者布尔标记来表达这种“可选”语义,这些方法都有其局限性和缺陷。std::optional的出现,让这种表达方式变得更加安全和直观。
1.1 std::optional的基本概念
std::optional可以看作是一个可能包含类型T的值的容器。它提供了一种检查是否存储了值的安全方式,并且可以用简洁的API访问该值或者处理值不存在的情况。
1.2 使用场景
- 函数可能无法返回有效值时
- 配置项可能未设置时
- 缓存结果可能不存在时
1.3 基本用法
#include <optional>
#include <iostream>
std::optional<int> maybeGetInt(bool flag) {
if (flag) {
return 123; // 返回有效的int
}
return {
}; // 返回一个空的optional
}
int main() {
auto val = maybeGetInt(true);
if (val) {
// 检查是否含有值
std::cout << "Value: " << *val << std::endl; // 解引用访问值
}
auto noVal = maybeGetInt(false);
std::cout << "No value: " << noVal.value_or(-1) << std::endl; // 使用value_or提供默认值
return 0;
}
2. std::any —— 类型安全的void*
对于需要存储任意类型的值,C++17提供了std::any。这个容器可以存储任何类型的单个值,并且能够在运行时安全地访问存储的值。这对于编写泛型代码或者需要类型擦除的场景非常有用。非常类似C语义中的void*,不过差别是void*本身不能直接存储对象,而std::any本身提供了存储对象的能力。当然,也可以用std::any存储指针类型。
2.1 std::any的基本概念
std::any可以存储任意类型的值,只要该类型是可复制的。使用std::any_cast可以试图取回原始类型的值,如果类型不匹配,会抛出std::bad_any_cast异常。
2.2 使用场景
- 动态类型的API设计
- 类型安全的容器
- 简化类型擦除实现
2.3 基本用法
#include <any&g