std::optional
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), orOptional(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
-1ornullptr: semantically unclear, and-1might 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
| Operation | Explanation |
|---|---|
opt.has_value() | whether there is a value |
opt.value() | Retrieve value (throw std::bad_optional_access if no value) |
*opt | Retrieve the value (undefined behavior if no value exists - check first!) |
opt.value_or(default) | Get value, return default value if none exists. |
opt = std::nullopt | Clear |
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.
| Example | Discusses what | Newly emerged syntax | Why write it this way | Precautions |
|---|---|---|---|---|
| Example 1 | optionally return search results | return i; / return std::nullopt;、if(opt)、*opt、value_or() | Optional makes "no value" a normal return, providing clear semantics. | Ensure there is a value before dereferencing *opt |
| Example 2 | optional indicates conversion failure | try/catch packaging + optional | Converting exceptions to empty optionals makes it easier for callers to handle. | You can also use std::from_chars (C++17) more efficiently. |
| Example 3 | optional as a member variable | std::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.”
| Scene | Recommendation |
|---|---|
| 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 message | Error 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.
使用建议
- 明确目标:在开始前确定您的具体需求,以便选择最合适的工具或教程。
- 充分利用资源:参考官方文档、教程和博客,这些资料能帮助您快速上手并解决问题。
- 实践应用:通过动手操作项目或编写代码来巩固学习成果,提升实际操作能力。
- 问题解决:遇到困难时,查阅参考资料或寻求社区支持,逐步培养独立解决问题的能力。
- 分享经验:完成项目后,可以撰写文章或博客分享心得,帮助其他学习者。
如果需要针对特定领域(如单片机、机器人或环境搭建)的进一步建议,请提供更多信息,我将为您细化内容。
- Functions that may "have no result" should return
std::optional<T>: safer than returning -1/nullptr. - Optional fields in structs: Using
optionalis more semantically clear than magic numbers. - Default to
value_or()instead of*opt:value_or()is safer. std::optionalhas extra space overhead: equivalent tosizeof(T) + sizeof(bool) + padding, minimal impact on small objects.- C++23 introduces monadic operations for
std::optional:.and_then(),.transform(),.or_else(), making chained calls more elegant. - 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 aTor is empty.- Resolved the "possibly no result" semantic issue for the function, replacing magic values.
- Use
has_value()orif(opt)for checking, and usevalue_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).