Wiki
std::optional
Existing English translations were reused. 5 current source blocks remain in Simplified Chinese, without triggering paid translation.
1.What problem does this section solve?
When a function returns a result, how is "no result" represented?
Common approaches each have their own drawbacks:
- Return
-1ornullptr: semantics are unclear, and-1may be a valid value. - Return
bool+ output parameter: the code is verbose and requires declaring a variable first. - Throw an exception: exceptions have overhead, and "not found" is often not an exceptional case.
std::optional<T>elegantly expresses the semantics of "may have a value, or may not have a value".
2.What is this feature?
std::optional<T> is a template class that either contains a value of type T or is empty (no value). It makes the information "may have no value" part of the type, and the compiler checks it for you.
3.C++ Standard Edition
C++17
4.Required header files
#include <optional>
5.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); // 若无值返回默认值
6.Common Usage
| 操作 | 说明 |
|---|---|
opt.has_value() |
是否有值 |
opt.value() |
获取值(无值抛 std::bad_optional_access) |
*opt |
获取值(无值行为未定义,先检查!) |
opt.value_or(default) |
获取值,无值返回默认值 |
opt = std::nullopt |
清空 |
opt.emplace(args...) |
原地构造值 |
7.Example code
7.1.Example 1: Using optional to indicate "may 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;
}
Running result:
Found Bob at index 1
David not found
Result: 1
Result2: 999
7.2.Example 2: Building on Example 1, use optional to represent a "conversion that may fail"
#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;
}
Running result:
"42" -> 42
"3.14" -> -1
"hello" -> -1
100 * 2 = 200
Here, thestd::stoifunction's conversion rule is: starting from the beginning of the string (which must begin with a digit), parse an integer as much as possible; as long as a part can be parsed, stoi itself is considered successful. For example:
- Convert
"123"string, the result is successful, which is123. - Convert
"123abc"string, the result is successful, which is123. - Convert
"3.14"string, the result is successful, which is3.
Theposparameters of this function indicatestd::stoiwhich position in the string was parsed, that is, the position of the first character that was not converted. For example:
- Convert
"123abc"string,valis 123, butposis 3. (Index starts from 0, as shown in the figure below)
123abc
012345
↑
pos = 3
- 转换
"3.14"字符串,val是3,但pos是1。(序号从0开始,如下图)
3.14
0123
↑
pos = 1
Here, thepos == s.size() is not valid, so returnstd::nullopt.
7.3.Example 3: Based on Example 2, use optional as a struct member (a field that "may be absent")
#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;
}
Running result:
Name: Alice
Age: 20
Nickname: Ali
Score: 95
---
Name: Bob
Age: 22
Nickname: (none)
Score: -1
---
8.Operation result
See the "run results" for each example above.
9.Key syntax explanation in the example
| Example | What was discussed | Newly introduced syntax | Why it is written this way | Notes |
|---|---|---|---|---|
| Example 1 | optional returns the lookup result | return i; / return std::nullopt;、if(opt)、*opt、value_or() |
optional makes "no value" a normal return, with clear semantics | Before dereferencing *opt , ensure there is a value |
| Example 2 | optional indicates conversion failure | try/catch wrapping + optional |
Convert exceptions into an empty optional, making it easier for callers to handle | You can also use std::from_chars (C++17), which is more efficient |
| Example 3 | optional as a member variable | std::optional<std::string> member |
For "optional fields", using optional is clearer than using magic values (such as -1) | optional itself takes extra space (size of T + bool + padding) |
10.optional is suitable for "may not have"; it is not suitable for "there are many error details".
optional It expresses: this value may or may not exist. It is not responsible for explaining "why it failed".
| 场景 | 推荐 |
|---|---|
| 查找名字,可能找不到 | std::optional<size_t> |
| 配置项可能没填 | std::optional<std::string> |
| 字符串转数字,只关心成不成功 | std::optional<int> |
| 打开文件失败,需要知道权限/路径/格式错误 | 错误码、异常、或 expected 类工具 |
| 网络请求失败,需要错误类型和错误消息 | 错误对象,不要只用 optional |
10.1.Example 4: The difference between optional and error information
#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;
}
Running result:
simple ok? no
error: port out of range
If the caller only cares about "whether there is a value,"optional is very suitable; if the caller needs to know the reason for failure, a separate optional is not enough.
11.Common Errors
Error 1: Do not check and directly *opt
std::optional<int> opt;
std::cout << *opt; // ❌ 未定义行为!opt 为空
Correct approach: first if (opt) check, or use opt.value_or(default).
错误 2:把 std::nullopt 和 nullptr 混淆
std::optional<int> opt = nullptr; // ❌ 编译错误!应该用 std::nullopt
Correct approach:opt = std::nullopt;
Error 3: Forgetting that value() will throw an exception when there is no value
std::optional<int> opt;
std::cout << opt.value(); // ❌ 抛出 std::bad_optional_access
The correct approach: use value_or(default) or check first.
12.Usage Recommendations
- 函数可能"没有结果"时返回
std::optional<T>:比返回 -1/nullptr 更安全。 - 结构体中"可选字段"用 optional:比 magic number 语义更清晰。
- 默认用
value_or()而不是*opt:value_or()更安全。 std::optional有额外空间开销:等于sizeof(T) + sizeof(bool) + padding,小对象影响不大。- C++23 引入了
std::optional的 monadic 操作:.and_then(),.transform(),.or_else()链式调用更优雅。 - 需要错误详情时别只用 optional:optional 只表达有没有值,不表达为什么没有。
13.Summary
std::optional<T>Either contains aT, or is empty.- Resolves the semantic problem that a function "may have no result", replacing magic value.
- Use
has_value()orif(opt)to check, and usevalue_or(default)to safely get the value. - Can be used for function return values, struct members, and function parameters (but optional is not recommended for parameters).