optional / variant / any
C++17 引入的三个类型安全工具,分别解决"可能没有值"、"多类型之一"、"任意类型"三个场景,是现代 C++ 替代裸指针和 void* 的利器。
std::optional — 可能没有值
cpp
#include <optional>
// 表示"可能有值,也可能没有"
std::optional<int> find_user_age(const std::string& name) {
if (name == "Alice") return 30;
return std::nullopt; // 没有值
}
auto age = find_user_age("Alice");
if (age.has_value()) {
std::cout << *age << "\n"; // 30
}
// 更简洁
if (age) {
std::cout << age.value() << "\n";
}
// 提供默认值
int val = age.value_or(0); // 没有值时返回 0
// 链式操作(C++23 monadic operations)
auto result = find_user_age("Alice")
.transform([](int age) { return age * 2; }) // 有值时变换
.or_else([]() -> std::optional<int> { return 18; }); // 无值时提供默认optional 的底层
cpp
// optional<T> 大约等价于:
// struct optional {
// bool has_value_;
// alignas(T) char storage_[sizeof(T)];
// };
// sizeof(optional<T>) == sizeof(T) + 1(加上对齐填充)
// 不分配堆内存!存储在对象内部
std::optional<std::string> opt = "hello";
// 字符串存储在 opt 对象内部(可能触发 SSO)
// 与指针对比
// optional<T>:值语义,拷贝时深拷贝,明确表达"可能没有"
// T*:指针语义,可能悬空,语义不清晰std::variant — 类型安全的联合体
cpp
#include <variant>
// 存储多种类型之一(类型安全的 union)
std::variant<int, double, std::string> v;
v = 42;
v = 3.14;
v = "hello";
// 访问
std::cout << std::get<std::string>(v) << "\n"; // "hello"
// std::get<int>(v); // 抛 std::bad_variant_access!
// 安全访问
if (auto* p = std::get_if<std::string>(&v)) {
std::cout << *p << "\n";
}
// 检查当前类型
std::cout << v.index() << "\n"; // 2(std::string 是第3个类型)
std::holds_alternative<std::string>(v); // true
// std::visit:访问者模式(推荐)
std::visit([](auto&& val) {
using T = std::decay_t<decltype(val)>;
if constexpr (std::is_same_v<T, int>)
std::cout << "int: " << val << "\n";
else if constexpr (std::is_same_v<T, double>)
std::cout << "double: " << val << "\n";
else
std::cout << "string: " << val << "\n";
}, v);variant 实战:错误处理
cpp
// 用 variant 实现 Result 类型(类似 Rust 的 Result<T, E>)
template<typename T, typename E>
using Result = std::variant<T, E>;
struct ParseError { std::string message; };
Result<int, ParseError> parse_int(std::string_view s) {
try {
return std::stoi(std::string(s));
} catch (...) {
return ParseError{"无效的整数: " + std::string(s)};
}
}
auto result = parse_int("42");
std::visit([](auto&& v) {
using T = std::decay_t<decltype(v)>;
if constexpr (std::is_same_v<T, int>)
std::cout << "成功: " << v << "\n";
else
std::cout << "错误: " << v.message << "\n";
}, result);
// 状态机(variant 的经典用途)
struct Idle {};
struct Running { int speed; };
struct Stopped { std::string reason; };
using State = std::variant<Idle, Running, Stopped>;
State state = Idle{};
state = Running{60};
state = Stopped{"用户中断"};std::any — 任意类型
cpp
#include <any>
// 存储任意类型(类型擦除)
std::any a = 42;
a = std::string("hello");
a = 3.14;
// 访问(必须知道确切类型)
std::cout << std::any_cast<double>(a) << "\n"; // 3.14
// std::any_cast<int>(a); // 抛 std::bad_any_cast!
// 安全访问
if (auto* p = std::any_cast<double>(&a)) {
std::cout << *p << "\n";
}
// 检查
a.has_value(); // true
a.type() == typeid(double); // true
a.reset(); // 清空
// any 有堆分配开销(小对象优化可能避免)
// 比 variant 慢,类型不安全(运行时检查)
// 适合:插件系统、配置值、异构容器三者对比
optional<T>:
用途:可能没有值的单一类型
开销:sizeof(T) + 1(无堆分配)
类型安全:编译期
示例:查找结果、可选参数
variant<T1,T2,...>:
用途:多种类型之一(编译期已知)
开销:max(sizeof(Ti)) + 1(无堆分配)
类型安全:编译期
示例:AST 节点、状态机、错误处理
any:
用途:任意类型(运行时决定)
开销:可能有堆分配
类型安全:运行时
示例:插件系统、配置值、Python 互操作关键认知
优先用 optional 表达"可能没有值",比返回 nullptr 或特殊值更清晰。variant 是类型安全的 union,配合 std::visit 实现访问者模式。any 是最后手段,有运行时开销且类型不安全。