C++17 中的 std::optional 与 std::variant 的实用案例

在 C++17 标准中,std::optional 与 std::variant 这两个类型提供了对传统指针与联合体的更安全、更高效的替代方案。它们在实际项目中被广泛用于表示可能为空的值以及类型安全的多态容器。本文通过一系列真实的代码示例,阐释这两种类型在项目中的应用场景、优势以及常见使用技巧。

1. std::optional 的基本使用

1.1 声明与初始化

#include <optional>
#include <string>

std::optional <int> findIndex(const std::string& str, char target) {
    for (size_t i = 0; i < str.size(); ++i) {
        if (str[i] == target) return static_cast <int>(i);
    }
    return std::nullopt; // 没找到时返回空值
}
  • 优势:与裸指针或魔法数相比,std::optional 明确表达“可能不存在”的语义,编译器可进行空值检查,避免了潜在的野指针错误。

1.2 访问方式

auto pos = findIndex("Hello, world!", 'o');
if (pos) {
    std::cout << "Found at: " << *pos << '\n';
} else {
    std::cout << "Character not found.\n";
}
  • 通过布尔上下文检查是否有值;若存在可使用 *.value() 取值。

1.3 与 std::optional 结合异常处理

int parseInt(const std::string& text) {
    try {
        return std::stoi(text);
    } catch (...) {
        return std::nullopt; // 转为 optional 统一处理
    }
}

在函数返回 `std::optional

` 时,可将异常转化为空值,调用方统一处理。 ## 2. std::variant 的多态容器 ### 2.1 基本声明 “`cpp #include #include #include using Var = std::variant; void printVar(const Var& v) { std::visit([](auto&& arg) { std::cout > parseConfig(const std::vector& lines) { std::vector> config; for (const auto& line : lines) { auto pos = line.find(‘=’); if (pos == std::string::npos) continue; std::string key = line.substr(0, pos); std::string value = line.substr(pos + 1); // 简单类型推断 if (auto* p = std::strchr(value.c_str(), ‘.’)) { config.emplace_back(key, std::stod(value)); } else if (std::all_of(value.begin(), value.end(), ::isdigit)) { config.emplace_back(key, std::stoi(value)); } else { config.emplace_back(key, value); } } return config; } “` 随后使用 `std::visit` 读取配置: “`cpp auto cfg = parseConfig(configLines); for (const auto& [k, v] : cfg) { std::cout >`,表示“可能存在但不确定类型”。例如: “`cpp using OptVar = std::optional>; OptVar getOptionalValue(bool flag) { if (flag) return std::variant{42}; return std::nullopt; } “` 访问时: “`cpp if (auto opt = getOptionalValue(true)) { std::visit([](auto&& val){ std::cout ` 的大小等于 `sizeof(T) + 1`(对齐后),因为需要存储一个布尔标记。若 `T` 本身很小,开销可忽略不计。 – `std::variant` 的大小等于 `sizeof(union)` + `sizeof(index_type)`,并对齐。对多类型容器的使用,尤其在缓存友好性方面表现优异。 ## 4. 进阶技巧 ### 4.1 对可选值的懒加载 “`cpp std::optional getLargeData() { // 只在第一次访问时才加载 static std::optional cache; if (!cache) { // 模拟昂贵操作 cache = std::make_optional(computeExpensive()); } return cache; } “` ### 4.2 通过 `std::variant` 代替传统联合体 “`cpp struct Shape { enum class Type { Circle, Rect, Triangle } type; std::variant /*rect*/, std::array /*tri*/> data; }; “` 此方式在运行时提供类型安全,同时保持了占位相对紧凑。 ## 5. 结语 `std::optional` 与 `std::variant` 为 C++17 带来了更安全、更可维护的方式来处理可能缺失的值和多态数据。它们的使用极大地减少了空指针错误和不安全的联合体访问,提高了代码的可读性与可维护性。希望通过本文提供的实战案例,能够帮助你在项目中快速上手并充分利用这两种强大的标准库组件。

发表评论