第 18.14 節

std::optional

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

What problem does this section solve?

When a function returns a result, how do you represent "no result"?

  • Using a special value like None (Python), null (Java/JavaScript), or Optional (Java).
  • In some cases, returning an empty collection (like an empty list) can signify no results.
  • For numeric results, you might use a special value like NaN (Not a Number).
  • Throwing an exception is another approach for unexpected "no result" cases.

Common approaches have their drawbacks:

  • Return -1 or nullptr: semantically unclear, and -1 might be a valid value.
  • Return bool + Output parameters: Code redundancy requires variables to be declared first.
  • Throw exceptions: exceptions have overhead, and "not found" is generally not considered an exceptional case.

std::optional<T> elegantly expresses the semantics of "may or may not have a value."

What is this feature?

std::optional<T> is a template class that either contains a value of type T or is empty (has no value). It makes the "might not have a value" information part of the type, and the compiler helps you check.

C++ standard version

C++17

Required header files

#include <optional>

Basic Syntax

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);           // 若无值返回默认值

Common Usage

OperationExplanation
opt.has_value()whether there is a value
opt.value()Retrieve value (throw std::bad_optional_access if no value)
*optRetrieve the value (undefined behavior if no value exists - check first!)
opt.value_or(default)Get value, return default value if none exists.
opt = std::nulloptClear
opt.emplace(args...)in-place construct value

Example code

Example 1: Using optional to represent "might not be found"

#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;
}

Results

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

Example 2: Building on Example 1, using optional to represent possibly failed conversion

#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;
}

Results

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

Example 3: Building on Example 2, using optional as a struct member (a field that "may not exist")

#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;
}

Results

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

runtime results

See the "running results" for each example above.

Key syntax explanation in the example

|Here is the translation of the provided Simplified Chinese Markdown fragment into natural American English, following all specified rules.


ExampleDiscusses whatNewly emerged syntaxWhy write it this wayPrecautions
Example 1optionally return search resultsreturn i; / return std::nullopt;if(opt)*optvalue_or()Optional makes "no value" a normal return, providing clear semantics.Ensure there is a value before dereferencing *opt
Example 2optional indicates conversion failuretry/catch packaging + optionalConverting exceptions to empty optionals makes it easier for callers to handle.You can also use std::from_chars (C++17) more efficiently.
Example 3optional as a member variablestd::optional<std::string> member"optional fields" is clearer than using magic values (like -1).optional itself occupies extra space (the size of T + bool + padding)

"Optional" is suitable for "might not exist" but not for "error details are many."

optional indicates that: this value may exist or may not. It doesn’t explain “why it failed.”

SceneRecommendation
Look up the name, it might not be found.std::optional<size_t>
The configuration items may not have been filled in.std::optional<std::string>
I18N_PROTECTED_0 Converting strings to numbers, only caring about whether it succeeds.std::optional<int>
Failed to open the file; this could be due to incorrect permissions, incorrect path, or format errors.Error codes, exceptions, or tools like expected
Network request failed, needs error type and error messageError object, don't just use optional.

Example 4: The Difference Between Optional and Error Messages

#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;
}

Results

simple ok? no
error: port out of range

If the caller only cares about "whether there's a value," optional is suitable; if the caller needs to know the reason for failure, a standalone optional isn't sufficient.

Common Errors

Error 1: No check performed directly *opt

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

Correct approach: First perform a microcontroller check, or use the environment setup.

Error 2: Confusing std::nullopt with nullptr

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

Correct approach: opt = std::nullopt;

Error 3: Forgetting value() will throw an exception when no value is present.

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

The correct approach is to use value_or(default) or check first.

使用建议

  • 明确目标:在开始前确定您的具体需求,以便选择最合适的工具或教程。
  • 充分利用资源:参考官方文档、教程和博客,这些资料能帮助您快速上手并解决问题。
  • 实践应用:通过动手操作项目或编写代码来巩固学习成果,提升实际操作能力。
  • 问题解决:遇到困难时,查阅参考资料或寻求社区支持,逐步培养独立解决问题的能力。
  • 分享经验:完成项目后,可以撰写文章或博客分享心得,帮助其他学习者。

如果需要针对特定领域(如单片机、机器人或环境搭建)的进一步建议,请提供更多信息,我将为您细化内容。

  1. Functions that may "have no result" should return std::optional<T>: safer than returning -1/nullptr.
  2. Optional fields in structs: Using optional is more semantically clear than magic numbers.
  3. Default to value_or() instead of *opt: value_or() is safer.
  4. std::optional has extra space overhead: equivalent to sizeof(T) + sizeof(bool) + padding, minimal impact on small objects.
  5. C++23 introduces monadic operations for std::optional: .and_then(), .transform(), .or_else(), making chained calls more elegant.
  6. When error details are needed, don't just use optional: optional only indicates whether a value exists, not why it doesn't.

Summary

  • std::optional<T> either contains a T or is empty.
  • Resolved the "possibly no result" semantic issue for the function, replacing magic values.
  • Use has_value() or if(opt) for checking, and use value_or(default) to safely get the value.
  • Can be used for function return values, struct members, and function parameters (though using optional for parameters is not recommended).
音乐页