在 C++17 之前,函数返回可选值常用的做法是返回指针、引用或使用自定义类型(如 boost::optional)。从 C++17 开始,标准库提供了 std::optional,它既能保持值类型的语义,又能显式表示“可能无值”的状态,避免了指针带来的空指针风险。
1. 基本使用
#include <optional>
#include <string>
#include <iostream>
std::optional<std::string> findUserName(int userId)
{
if (userId == 42) {
return std::string("Alice");
}
// 没有对应用户
return std::nullopt; // 或者直接 return {};
}
int main()
{
auto nameOpt = findUserName(42);
if (nameOpt) {
std::cout << "User name: " << *nameOpt << '\n';
} else {
std::cout << "User not found.\n";
}
}
std::nullopt用来表示“无值”。- 通过
operator bool()判断是否包含值。 *nameOpt或nameOpt.value()获取存储的值。
2. 结合 std::variant 与 std::optional
有时需要同时返回错误码和成功值。可以用 std::variant 包装错误类型,或者使用 std::optional<std::variant<Error, Success>>。
enum class Error { NotFound, PermissionDenied };
std::optional<std::variant<Error, std::string>> findUserNameV2(int userId)
{
if (userId == 42) {
return std::string("Alice");
}
if (userId < 0) {
return Error::PermissionDenied;
}
return Error::NotFound;
}
调用时:
auto result = findUserNameV2(42);
if (result) {
if (auto *name = std::get_if<std::string>(&*result)) {
std::cout << "Name: " << *name << '\n';
} else if (auto *err = std::get_if <Error>(&*result)) {
std::cout << "Error: " << static_cast<int>(*err) << '\n';
}
}
3. 避免拷贝:使用 std::optional<std::reference_wrapper>
如果想返回对已有对象的引用而不复制,可以:
std::optional<std::reference_wrapper<int>> findNumber(std::vector<int>& vec, int target)
{
auto it = std::find(vec.begin(), vec.end(), target);
if (it != vec.end()) {
return std::ref(*it); // 包装引用
}
return std::nullopt;
}
使用时:
auto optRef = findNumber(myVec, 10);
if (optRef) {
optRef->get() *= 2; // 直接修改原始元素
}
4. 与异常的关系
std::optional 与异常是两种不同的错误处理方式。通常建议:
- 对于可恢复、频繁出现的情况,使用
std::optional或std::expected(C++23)。 - 对于不可恢复、逻辑错误,抛异常。
5. 性能注意
- `std::optional ` 的大小等于 `T` + 一位布尔值。若 `T` 很大,使用 `std::optional>` 或 `std::optional>` 以减少拷贝。
std::optional的构造、拷贝、移动都与T的操作相对应;若T没有显式默认构造,使用 `std::optional ` 时请确保 `T` 可默认构造或使用 `std::in_place`。
6. 小结
- `std::optional ` 是一种安全、易读的可选值容器。
- 与
std::nullopt结合使用,可显式表达“无值”。 - 与
std::variant、std::reference_wrapper等组合,可实现更复杂的返回类型。 - 适当选择
optional与异常的平衡点,写出更可靠的 C++ 代码。