C++ 17 中的 std::optional 如何正确使用?

std::optional 是 C++ 17 引入的一个非常有用的容器,用来表示“可能存在也可能不存在”的值。它在处理可空值、错误返回、懒加载等场景中表现尤为出色。下面我们将从基本使用、常见坑、性能考虑以及高级用法几个方面来全面剖析 std::optional


1. 基本使用

1.1 定义和初始化

#include <optional>
#include <string>
#include <iostream>

std::optional <int> findIndex(const std::string& s, char target) {
    for (size_t i = 0; i < s.size(); ++i) {
        if (s[i] == target) return static_cast <int>(i);
    }
    return std::nullopt;               // 代表“未找到”
}

int main() {
    auto idx = findIndex("hello", 'e');
    if (idx) {
        std::cout << "Index: " << *idx << '\n';
    } else {
        std::cout << "Character not found.\n";
    }
}
  • `std::optional ` 通过 `std::nullopt` 初始化为空状态。
  • 通过 has_value()operator bool() 或直接 if (opt) 判断是否包含值。
  • 访问值:*optopt.value()。若为空,opt.value() 会抛出 std::bad_optional_access

1.2 默认值

int value = idx.value_or(-1);  // 若无值,返回 -1

value_or 可以一次性提供一个默认值,避免显式的 if 分支。


2. 常见坑与注意事项

位置 问题 解决方案
构造 `std::optional
o(5);| 直接传入可空值;如果想传std::nullopt需显式std::nullopt`
赋值 o = std::nullopt; 该语法可用,o 变为空状态
复制 复制 std::optional<std::string> 会复制字符串 若不想复制,可使用 std::optional<std::string_view>
传参 foo(opt) 需要考虑是否会拷贝 用 `const std::optional
&传参,或移动语义std::move(opt)`

3. 性能考量

  • `std::optional ` 的大小等于 `sizeof(T)` + 1 字节(对齐后)。如果 `T` 很大,`optional` 仍然占据同样空间。
  • 访问时不需要额外的 new/delete,与 std::unique_ptr 的动态分配不同。
  • 需要时使用 std::optional<std::reference_wrapper<T>>std::optional<T*> 来避免复制。

4. 高级用法

4.1 与 std::variant 结合

using Result = std::variant<std::string, std::runtime_error>;
Result parse(const std::string& input) {
    if (input.empty())
        return std::runtime_error("empty input");
    else
        return input;
}

此时可以通过 `std::holds_alternative

` 判断错误与成功。 ### 4.2 作为 API 返回值 “`cpp std::optional> readFile(const std::string& path) { std::ifstream in(path); if (!in) return std::nullopt; // 读取… } “` 返回 `nullopt` 直接表示“文件不存在或读取失败”,不必抛异常。 ### 4.3 用于链式调用 “`cpp auto chain = [](std::optional a, std::optional b) { return a && b ? std::optional {a.value() + b.value()} : std::nullopt; }; “` 这样可以在多个可空值存在时才进行计算。 — ## 5. 与第三方库的协作 – **Boost.Optional**:C++17 标准化前,Boost 已经提供了类似功能。若项目仍使用 Boost,可无缝迁移。 – **std::expected**(C++23):类似 `optional` 的错误信息包装。将 `optional` 与 `expected` 结合可在 API 里分别传递“无结果”和“错误信息”。 — ## 6. 代码示例:实现一个简易配置解析器 “`cpp #include #include #include #include class Config { std::unordered_map data_; public: void set(const std::string& key, const std::string& val) { data_[key] = val; } std::optional get(const std::string& key) const { auto it = data_.find(key); if (it == data_.end()) return std::nullopt; return it->second; } // 读取整数 std::optional getInt(const std::string& key) const { if (auto val = get(key)) { try { return std::stoi(*val); } catch (…) { return std::nullopt; // 转换失败 } } return std::nullopt; } }; int main() { Config cfg; cfg.set(“port”, “8080”); cfg.set(“timeout”, “30”); if (auto port = cfg.getInt(“port”)) { std::cout

发表评论