第 18.14 節

std::optional

0瀏覽次數0訪問次數--跳出率--平均停留

本節解決什麼問題

函數返回結果時,如何表示"沒有結果"?

常見的做法各有缺點:

  • 返回 -1nullptr:語義不清晰,且 -1 可能是合法值。
  • 返回 bool + 輸出參數:代碼冗長,需要先聲明變量。
  • 拋出異常:異常有開銷,且"查不到"往往不算異常情況。

std::optional<T> 優雅地表達了"可能有值、也可能沒有值"的語義。

這個特性是什麼

std::optional<T> 是一個模板類,它要麼包含一個 T 類型的值,要麼為空(沒有值)。它讓"可能沒有值"這個信息成為類型的一部分,編譯器幫你檢查。

C++ 標準版本

C++17

需要的頭文件

#include <optional>

基本語法

std::optional<T> opt;                    // 默认空
std::optional<T> opt = value;            // 有值
std::optional<T> opt = std::nullopt;     // 显式空

// 检查是否有值
if (opt.has_value()) { ... }
if (opt) { ... }                         // 隐式转 bool

// 获取值
T val = opt.value();                     // 若无值抛异常
T val = *opt;                            // 若无值是未定义行为
T val = opt.value_or(default);           // 若无值返回默认值

常用用法

操作説明
opt.has_value()是否有值
opt.value()獲取值(無值拋 std::bad_optional_access
*opt獲取值(無值行為未定義,先檢查!)
opt.value_or(default)獲取值,無值返回默認值
opt = std::nullopt清空
opt.emplace(args...)原地構造值

示例代碼

示例 1:用 optional 表示"可能找不到"

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

// 在 vector 中查找元素,返回位置(可能找不到)
std::optional<size_t> find_index(const std::vector<std::string>& v,
                                  const std::string& target)
{
    for (size_t i = 0; i < v.size(); ++i)
    {
        if (v[i] == target)
        {
            return i;  // 找到了
        }
    }
    return std::nullopt;  // 没找到
}

int main()
{
    std::vector<std::string> names = {"Alice", "Bob", "Charlie"};

    auto idx = find_index(names, "Bob");
    if (idx)
    {
        std::cout << "Found Bob at index " << *idx << "\n";
    }

    auto idx2 = find_index(names, "David");
    if (!idx2)
    {
        std::cout << "David not found\n";
    }

    // 使用 value_or 提供默认值
    std::cout << "Result: " << idx.value_or(999) << "\n";
    std::cout << "Result2: " << idx2.value_or(999) << "\n";

    return 0;
}

運行結果

Found Bob at index 1
David not found
Result: 1
Result2: 999

示例 2:在示例 1 基礎上,用 optional 表示"可能失敗的轉換"

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

// 字符串转整数,失败返回 nullopt
std::optional<int> to_int(const std::string& s)
{
    try
    {
        size_t pos;
        int val = std::stoi(s, &pos);
        if (pos == s.size())  // 整个字符串都被转换了
        {
            return val;
        }
    }
    catch (...) {}
    return std::nullopt;
}

int main()
{
    auto r1 = to_int("42");
    auto r2 = to_int("3.14");  // 带小数点
    auto r3 = to_int("hello"); // 不是数字

    std::cout << "\"42\" -> " << r1.value_or(-1) << "\n";
    std::cout << "\"3.14\" -> " << r2.value_or(-1) << "\n";
    std::cout << "\"hello\" -> " << r3.value_or(-1) << "\n";

    // 用 if 检查
    if (auto r = to_int("100"); r)
    {
        std::cout << "100 * 2 = " << (*r * 2) << "\n";
    }

    return 0;
}

運行結果

"42" -> 42
"3.14" -> -1
"hello" -> -1
100 * 2 = 200

示例 3:在示例 2 基礎上,用 optional 作為結構體成員("可能沒有"的字段)

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

struct Person
{
    std::string name;
    int age;
    std::optional<std::string> nickname;  // 可能没有昵称
    std::optional<int> score;              // 成绩可能尚未录入
};

void print_person(const Person& p)
{
    std::cout << "Name: " << p.name << "\n";
    std::cout << "Age: " << p.age << "\n";

    std::cout << "Nickname: " << p.nickname.value_or("(none)") << "\n";
    std::cout << "Score: " << p.score.value_or(-1) << "\n";

    std::cout << "---\n";
}

int main()
{
    Person p1{"Alice", 20, "Ali", 95};
    Person p2{"Bob", 22, std::nullopt, std::nullopt};  // 没有昵称和成绩

    print_person(p1);
    print_person(p2);

    return 0;
}

運行結果

Name: Alice
Age: 20
Nickname: Ali
Score: 95
---
Name: Bob
Age: 22
Nickname: (none)
Score: -1
---

運行結果

見上方每個示例的"運行結果"。

示例中的關鍵語法解釋

示例講了什麼新出現的語法為什麼這樣寫注意事項
示例 1optional 返回查找結果return i; / return std::nullopt;if(opt)*optvalue_or()optional 讓"沒有值"成為正常返回,語義清晰解引用 *opt 前要確保有值
示例 2optional 表示轉換失敗try/catch 包裝 + optional把異常轉換成空 optional,調用者更易處理也可以用 std::from_chars(C++17)更高效
示例 3optional 作為成員變量std::optional<std::string> 成員"可選字段"用 optional 比用 magic value(如 -1)更清晰optional 本身佔用額外空間(T 的大小 + bool + padding)

optional 適合"可能沒有",不適合"錯誤詳情很多"

optional 表達的是:這個值可能存在,也可能不存在。它不負責説明“為什麼失敗”。

場景推薦
查找名字,可能找不到std::optional<size_t>
配置項可能沒填std::optional<std::string>
字符串轉數字,只關心成不成功std::optional<int>
打開文件失敗,需要知道權限/路徑/格式錯誤錯誤碼、異常、或 expected 類工具
網絡請求失敗,需要錯誤類型和錯誤消息錯誤對象,不要只用 optional

示例 4:optional 和錯誤信息的區別

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

// optional 表示“可能有值,也可能没有值”的结果。
std::optional<int> parse_port_simple(const std::string& text)
{
    try
    {
        int port = std::stoi(text);
        if (port >= 0 && port <= 65535)
        {
            return port;
        }
    }
    catch (...) {}
    return std::nullopt;
}

struct ParseResult
{
    bool ok;
    int value;
    std::string error;
};

ParseResult parse_port_with_error(const std::string& text)
{
    try
    {
        int port = std::stoi(text);
        if (port < 0 || port > 65535)
        {
            return {false, 0, "port out of range"};
        }
        return {true, port, ""};
    }
    catch (...)
    {
        return {false, 0, "not a number"};
    }
}

int main()
{
    // 程序从 main 函数开始执行,下面的语句会按顺序运行。
    auto simple = parse_port_simple("70000");
    std::cout << "simple ok? " << (simple ? "yes" : "no") << "\n";

    auto detailed = parse_port_with_error("70000");
    if (!detailed.ok)
    {
        std::cout << "error: " << detailed.error << "\n";
    }

    return 0;
}

運行結果

simple ok? no
error: port out of range

如果調用者只關心“有沒有值”,optional 很合適;如果調用者需要知道失敗原因,單獨的 optional 就不夠了。

常見錯誤

錯誤 1:不檢查直接 *opt

std::optional<int> opt;
std::cout << *opt;  // ❌ 未定义行为!opt 为空

正確做法:先 if (opt) 檢查,或用 opt.value_or(default)

錯誤 2:把 std::nulloptnullptr 混淆

std::optional<int> opt = nullptr;  // ❌ 编译错误!应该用 std::nullopt

正確做法:opt = std::nullopt;

錯誤 3:忘記 value() 會在無值時拋異常

std::optional<int> opt;
std::cout << opt.value();  // ❌ 抛出 std::bad_optional_access

正確做法:用 value_or(default) 或先檢查。

使用建議

  1. 函數可能"沒有結果"時返回 std::optional<T>:比返回 -1/nullptr 更安全。
  2. 結構體中"可選字段"用 optional:比 magic number 語義更清晰。
  3. 默認用 value_or() 而不是 *optvalue_or() 更安全。
  4. std::optional 有額外空間開銷:等於 sizeof(T) + sizeof(bool) + padding,小對象影響不大。
  5. C++23 引入了 std::optional 的 monadic 操作.and_then(), .transform(), .or_else() 鏈式調用更優雅。
  6. 需要錯誤詳情時別隻用 optional:optional 只表達有沒有值,不表達為什麼沒有。

小結

  • std::optional<T> 要麼包含一個 T,要麼為空。
  • 解決了函數"可能沒結果"的語義問題,替代 magic value。
  • has_value()if(opt) 檢查,用 value_or(default) 安全獲取值。
  • 可用於函數返回值、結構體成員、函數參數(但參數不推薦用 optional)。
音乐页