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 适合提供默认值。