optional

学习定位:现代 C++ 核心知识。std::optional 是 C++17 起日常开发很常见的返回值表达方式,C++20 路线中应作为“可能没有值”的首选工具之一。

一句话:表示“可能有值,也可能没值”,替代 -1 / nullptr / 输出参数。

核心操作

操作代码示例说明
创建空值std::optional<int> o;
std::optional<int> o = std::nullopt;
初始无值
创建有值std::optional<int> o = 42;
auto o = std::make_optional(42);
直接存值
判断是否有值if (o) { ... }
if (o.has_value()) { ... }
用于分支判断
取值(不检查)int x = *o;无值时 未定义行为,必须自己保证有值
取值(抛异常)int x = o.value();无值时抛 std::bad_optional_access
取值或默认值int x = o.value_or(0);无值时返回默认值
重置为空o.reset();
o = std::nullopt;
清空
原地构造o.emplace(42);避免临时对象

适用场景

1. 查找/解析函数可能失败

std::optional<int> parse_int(const std::string& s) {
    if (s.empty()) return std::nullopt;
    return std::stoi(s);   // 简化,实际要处理异常
}
 
// 使用
if (auto num = parse_int("123")) {
    std::cout << "got " << *num << '\n';
} else {
    std::cout << "invalid\n";
}

2. 类的可选成员(配置项未设置)

struct Config {
    std::optional<int> timeout;   // 未设置时无值
    std::optional<std::string> log_file;
};
 
Config cfg;
if (cfg.timeout) {
    use_timeout(*cfg.timeout);
} else {
    use_default_timeout();
}

3. 避免输出参数(让代码更清晰)

// 传统写法(不推荐)
bool divide(int a, int b, int& result);
 
// optional 写法
std::optional<int> divide(int a, int b) {
    if (b == 0) return std::nullopt;
    return a / b;
}

4. 缓存 / 懒加载

class HeavyObject {
    std::optional<Expensive> cache;
public:
    const Expensive& get() {
        if (!cache) cache = Expensive::compute();
        return *cache;
    }
};

避坑点

  • 不要存引用(目前标准不支持)。若需要可选引用,用 T*

    // 错误:std::optional<int&> ref;
    // 正确:int* ptr = nullptr;
  • *opt 前必须检查

    std::optional<int> o;
    int x = *o;          // 未定义行为!崩溃
  • optional<bool> 容易被误解(三种状态:空、true、false):

    std::optional<bool> flag;
    if (flag) { ... }             // 只检查是否有值,不检查值本身
    if (flag.value_or(false)) { ... }  // 要检查值时这样写
  • 大对象放进 optional 不会自动变高效:拷贝时仍会拷贝整个对象。可用 std::optional<std::unique_ptr<T>>std::optional<T&>(C++26)。


总结记忆:看见“可能没值”,优先考虑 optional;取前先 if (opt);用 * 要自己保证有值,value_or 适合提供默认值。