在 C++17 标准中加入了 std::optional,提供了一种优雅的方式来表示可选值或“可能为空”的对象。它相当于一种“安全的空指针”,但不需要指针的间接引用。下面从概念、用法、性能、错误场景和最佳实践四个方面,帮助你更好地掌握 std::optional。
1. 概念与语义
- 类型包装:`std::optional ` 包装了类型 `T`,它可以处于两种状态:**有值**(engaged)或**无值**(disengaged)。
- 默认构造:默认构造一个
optional时,它处于无值状态;可以显式地使用std::nullopt表示无值。 - 访问:使用
operator*、operator->或value()访问值;如果无值则抛出std::bad_optional_access。或者使用value_or(default_value)直接提供默认值。
2. 常见用例
2.1 作为返回值
std::optional <int> find_in_vector(const std::vector<int>& vec, int target) {
for (int v : vec) {
if (v == target) return v; // 自动构造 optional <int>,有值
}
return std::nullopt; // 说明未找到
}
2.2 作为函数参数
void set_threshold(std::optional <double> threshold) {
if (threshold) {
// 有指定阈值
global_threshold = *threshold;
} else {
// 使用默认阈值
global_threshold = DEFAULT_THRESHOLD;
}
}
2.3 链式调用与组合
std::optional<std::string> read_file(const std::string& path) {
// ...
}
auto content = read_file("a.txt");
if (content) {
// 处理 content.value()
}
3. 性能考虑
- 大小:大多数实现将 `optional ` 的大小约为 `sizeof(T) + 1`,加上对齐。若 `T` 很大,可考虑使用 `std::optional>` 或者指针包装。
- 构造成本:构造/销毁
optional与对应类型相同,除非 T 有显式构造/析构。无值时不调用 T 的构造。 - 对齐:使用
alignas保证正确对齐,尤其是在自定义大对象时。
4. 常见陷阱
-
访问未赋值的 optional
std::optional <int> opt; std::cout << *opt; // UB / 运行时抛异常解决:先检查
if (opt)或使用value_or()。 -
拷贝构造导致无值
std::optional <int> a{5}; std::optional <int> b = a; // b 也有值 std::optional <int> c = std::move(a); // a 仍然保持值optional的移动并不影响原对象。若想“消费”可用std::exchange。 -
与指针混用导致误解
optional<T*>不是 “非空指针”,它仍然可以是nullptr。若想确保非空指针,使用T*并在函数签名中注明。 -
默认值的隐含性能
value_or(default)会拷贝或移动默认值,若默认值代价大,建议使用value_or_else(C++23)或std::optional::value_or+ lambda。
5. 最佳实践
| 场景 | 推荐方式 |
|---|---|
| 需要“空”值的返回 | 直接使用 `std::optional |
,返回std::nullopt` 或值 |
|
| 需要“可选”参数 | `std::optional |
或std::optional<std::reference_wrapper>` |
|
| 需要避免拷贝 | 对大对象使用 optional<std::reference_wrapper<T>> 或 optional<std::unique_ptr<T>> |
与 std::variant 结合 |
std::variant<T, std::monostate> 与 optional 的区别是:optional 只有两种状态,而 variant 可以有多种状态 |
| 兼容旧代码 | 只在内部使用 optional,对外接口保持普通指针或返回值 |
6. 小结
std::optional是处理“可能为空”情况的现代、类型安全的工具。- 它的使用让代码更具可读性,减少空指针错误。
- 在使用时要注意访问前的检查,避免不必要的拷贝。
- 结合
value_or,value_or_else,operator*等,构建安全、高效的代码。
掌握好这些细节,你就能在 C++17 及以后版本中自如地使用 std::optional,写出更安全、更优雅的程序。