C++ std::optional详解-薛定谔的“盒子”!
- 一、std::optional详解
-
- 1、什么是 `std::optional`?
- 2、为什么需要 `std::optional`?
- 3、核心操作与成员函数
-
- 3.1 、构造与赋值
- 3.2、 状态检查
- 3.3、 值访问
- 3.4 、修改与销毁
- 3.5、访问包含的值 (C++23 新增)
- 4、 典型使用场景
- 5、注意事项
- 6、 示例代码
- 二、代码示例

一、std::optional详解
1、什么是 std::optional?
std::optional 是 C++17 引入的一个模板类,它用于表示一个 可能包含值,也可能不包含值(即为“空”) 的封装。它提供了一种类型安全、表达清晰的方式来处理那些不总是有有效结果的操作(比如查找、解析、计算等)。
- 核心思想:一个 std::optional<T> 对象要么持有一个类型为 T 的值,要么什么都不持有(表示为 std::nullopt)。
- 替代方案:在引入 std::optional 之前,通常使用特殊值(如 -1, nullptr, std::string::npos)、bool 标志位加一个值变量、或返回指针(可能为 nullptr)等方式来表示可选值。std::optional 提供了一种更标准、更安全、更易于理解的方式。
2、为什么需要 std::optional?
- 类型安全:明确区分有值和无值的状态,编译器可以检查,避免误用无效值。
- 表达清晰:代码意图更明确,看到 std::optional 就知道这个值可能不存在。
- 避免魔术值:不再需要使用特定的、有时难以记忆或容易混淆的“无效值”来表示缺失。
- 更好的接口设计:函数可以清晰地返回一个可能不存在的结果,而不是通过输出参数或异常(在某些场景下异常可能太重)来传递状态。
- 效率:通常,std::optional<T> 的内存占用是 sizeof(T) + sizeof(bool) 加上可能的对齐填充。它通常比使用动态内存分配(如 std::unique_ptr)更轻量。
3、核心操作与成员函数
3.1 、构造与赋值
- 默认构造:创建一个不包含值的 optional 对象(空状态)。std::optional<int> optInt; // 空的 optional
- std::nullopt 构造:显式构造一个空 optional。std::optional<std::string> optStr = std::nullopt;
- 值初始化:用给定的值构造一个包含该值的 optional。std::optional<double> optDbl(3.14159); // 包含 pi
- std::in_place 构造:用于直接原地构造包含的对象,特别是当 T 的构造函数需要多个参数,或者你想避免临时对象时。std::optional<std::complex<double>> optComplex(std::in_place, 1.0, 2.0); // 构造 complex(1.0, 2.0)
- 拷贝/移动构造与赋值:支持从另一个 optional 拷贝或移动。
- 赋值:可以使用 = 来赋值一个值、std::nullopt 或另一个 optional。optInt = 42; // 现在包含 42
optInt = std::nullopt; // 变为空
3.2、 状态检查
- has_value() / operator bool:检查 optional 是否包含值。两者通常等价。if (optInt.has_value()) { /* 有值 */ }
if (optInt) { /* 有值,常用这种简洁写法 */ } - operator == std::nullopt:检查是否为空。if (optStr == std::nullopt) { /* 空 */ }
3.3、 值访问
警告:在 optional 为空时访问其值是未定义行为(通常会导致程序崩溃或不可预知的结果)。访问前必须检查是否有值。
- value():返回包含值的引用。如果为空,则抛出 std::bad_optional_access 异常。try {
int x = optInt.value(); // 安全访问,可能抛异常
} catch (const std::bad_optional_access& e) {
// 处理空的情况
} - operator*:返回包含值的引用。不检查是否为空!仅在确定有值时使用。if (optDbl) {
double y = *optDbl; // 解引用访问
} - operator->:返回指向包含值的指针。不检查是否为空!仅在确定有值时使用。std::optional<std::vector<int>> optVec;
// … 可能给 optVec 赋值一个 vector …
if (optVec) {
size_t sz = optVec->size(); // 使用 -> 访问成员
} - value_or(U&& default_value):如果包含值,则返回该值;否则返回提供的 default_value。这是安全获取值的便捷方式。int safeValue = optInt.value_or(–1); // 如果 optInt 为空,则返回 -1
3.4 、修改与销毁
- emplace(args…):在原地构造一个新值(替换当前可能存在的值)。参数 args… 传递给 T 的构造函数。optStr.emplace("Hello, Optional!"); // 构造一个 string
- reset():销毁包含的值(如果存在),并将 optional 置为空状态。等价于 opt = std::nullopt;。optComplex.reset(); // 现在为空
- swap:交换两个 optional 对象的内容。
3.5、访问包含的值 (C++23 新增)
- and_then:如果包含值,则应用给定的函数到该值上(该函数应返回另一个 optional),否则返回空 optional。用于链式调用。
- transform:如果包含值,则应用给定的函数到该值上(该函数返回一个新类型的值),并将结果包装在 optional 中返回;否则返回空 optional。
- or_else:如果为空,则调用给定的函数(该函数应返回一个 optional),否则返回当前 optional。
4、 典型使用场景
// … 查找逻辑 …
if (found) {
return found_id;
}
return std::nullopt; // 未找到
}
auto idOpt = find_id_by_name("Alice");
if (idOpt) {
use_id(*idOpt);
}
try {
return std::stoi(str);
} catch (...) {
return std::nullopt;
}
}
5、注意事项
- 访问前检查:这是最重要的规则!永远不要对可能为空的 optional 使用 operator* 或 operator->。优先考虑 value_or 或先检查 has_value()/operator bool。
- 性能:std::optional 通常没有动态内存分配的开销。其大小通常是 sizeof(T) + sizeof(bool) 加上可能的对齐填充。对于小对象,效率很高。
- 与 std::variant 的区别:std::optional<T> 可以看作是 std::variant<std::monostate, T> 的一个特例和简化。std::variant 用于表示多个可能类型中的一个。
- 与 std::expected (C++23提案) 的区别:std::expected<T, E> 用于表示一个可能包含值 T 或错误 E 的结果,比 optional 能携带更多错误信息。
6、 示例代码
#include <optional>
#include <iostream>
#include <string>
std::optional<std::string> create_greeting(bool formal) {
if (formal) {
return "Good day"; // 包含值
}
return std::nullopt; // 无问候语
}
int main() {
auto greetingOpt = create_greeting(true);
// 检查并安全访问
if (greetingOpt) {
std::cout << *greetingOpt << ", Sir/Madam!" << std::endl;
}
else {
std::cout << "No greeting today." << std::endl;
}
// 使用 value_or
std::cout << "Greeting: " << greetingOpt.value_or("(none)") << std::endl;
// 尝试访问空 optional (危险!)
auto emptyOpt = create_greeting(false);
if (emptyOpt) {
std::cout << *greetingOpt << ", Tom/Jake!" << std::endl;
}
else {
std::cout << "No greeting today." << std::endl;
}
return 0;
}
运行结果: 
二、代码示例
#include <iostream>
#include <optional>
int main() {
// 创建一个 std::optional<int> 变量,初始化为空
std::optional<int> maybe_value;
// 检查值是否存在
if (maybe_value.has_value()) {
std::cout << "Value: " << maybe_value.value() << std::endl;
} else {
std::cout << "No value present" << std::endl;
}
// 设置值
maybe_value = 10; // 现在包含值 10
// 再次检查
if (maybe_value) { // 使用布尔上下文检查
std::cout << "Value after assignment: " << *maybe_value << std::endl; // 使用解引用操作符
}
// 重置为无值
maybe_value.reset();
// 尝试访问值(可能抛出 std::bad_optional_access 异常)
try {
std::cout << "Value after reset: " << maybe_value.value() << std::endl;
} catch (const std::bad_optional_access& e) {
std::cout << "Error: " << e.what() << std::endl;
}
return 0;
}
运行结果
No value present
Value after assignment: 10
Value after reset: Error: Bad optional access
C:\\Users\\徐鹏\\Desktop\\新建文件夹\\Project1\\x64\\Debug\\Project1.exe (进程 10380)已退出,代码为 0 (0x0)。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .

代码解释

网硕互联帮助中心






评论前必须登录!
注册