Skip to content

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 是最后手段,有运行时开销且类型不安全。

系统学习 C++ 生态,深入底层架构