云计算百科
云计算领域专业知识百科平台

C++ std::optional详解-薛定谔的“盒子”!

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、 典型使用场景

  • 函数返回可能无效的结果:std::optional<int> find_id_by_name(const std::string& name) {
    // … 查找逻辑 …
    if (found) {
    return found_id;
    }
    return std::nullopt; // 未找到
    }
    auto idOpt = find_id_by_name("Alice");
    if (idOpt) {
    use_id(*idOpt);
    }
  • 解析/转换可能失败:std::optional<int> safe_stoi(const std::string& str) {
    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)
    要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
    按任意键关闭此窗口. . .

    在这里插入图片描述

    代码解释

  • 声明 std::optional:在代码开头,我们声明了一个 std::optional<int> 变量 maybe_value,初始化为空(不包含值)。
  • 检查值存在:使用 has_value() 方法或直接使用 if (maybe_value) 检查值是否存在。第一次检查时,变量为空,输出 “No value present”。
  • 设置值:通过赋值操作 maybe_value = 10,将值设置为 10。
  • 访问值:使用 value() 方法或解引用操作符 * 获取值。如果值存在,输出当前值。
  • 重置值:调用 reset() 方法使 optional 变量再次为空。
  • 异常处理:当尝试访问一个空的 optional 值时,value() 方法会抛出 std::bad_optional_access 异常。我们使用 try-catch 块捕获并处理这个异常。
  • 在这里插入图片描述

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » C++ std::optional详解-薛定谔的“盒子”!
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!