C++ std::optional

一、基本概念

C++17 引入了 std::optional,表示一个可能有值的对象(没有值时就是默认的 std::nullopt)。它提供了一种类型安全的方式来表示可能存在也可能不存在的值

std::optional 出现之前,我们通常使用魔术值(如 -1nullptr、空字符串)或通过指针/引用参数来表达“无值”的概念。这些方法要么不安全,要么语义模糊。std::optional 完美地解决了这些痛点。

std::optional 是一个模板类,它要么包含一个类型为 T 的值,要么为空(不包含任何值)。

  • 空状态可以用 std::nullopt 表示
  • std::optional 不需要动态内存分配(堆分配),它的值直接存储在 std::optional 对象内部(栈上),因此性能非常高

二、基本用法

初始化与创建:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <optional>
#include <string>

void createOptional() {
// 1. 创建一个空对象
std::optional<int> emptyOpt;
std::optional<int> emptyOpt2 = std::nullopt;

// 2. 创建一个有值的对象
std::optional<int> valuedOpt = 42;
std::optional<std::string> stringOpt{"Hello World"};

// 3. 使用 std::make_optional(推荐,类似于 std::make_shared)
auto floatOpt = std::make_optional<float>(3.14f);

// 4. 采用 {} 初始化再调用 emplace 插入值
// 每次调用emplace 时,会清除掉之前的值,因此可以多次调用,且能保证每次都是最新的数值。
std::optional<int> val1 = {};
val1.emplace(128);
}

检查与访问值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void accessOptional(const std::optional<std::string>& opt) {
// 1. 检查是否有值
if (opt.has_value()) { // 或者直接 if (opt != std::nullopt)
// 2. 使用 operator* 或 operator-> 访问值(不进行安全检查,速度快)
std::cout << "Value: " << *opt << std::endl;
}

// 3. 使用 .value() 访问(安全,若无值会抛出 std::bad_optional_access 异常)
try {
std::cout << "Value: " << opt.value() << std::endl;
} catch (const std::bad_optional_access& e) {
std::cerr << "Error: " << e.what() << std::endl;
}

// 4. 使用 .value_or() 提供默认值(极力推荐,最优雅安全)
// 如果 opt 有值则返回该值,否则返回 "Default"
std::string safeValue = opt.value_or("Default");
std::cout << "Safe Value: " << safeValue << std::endl;
}

三、简单示例

在实际开发中,std::optional 最常用的场景是函数的返回值,用以表明查找、解析等操作可能失败。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <iostream>
#include <optional>
#include <string>
#include <unordered_map>

struct User {
int id;
std::string name;
};

class UserRepository {
private:
std::unordered_map<int, User> _users = {
{1, {1, "Alice"}},
{2, {2, "Bob"}}
};

public:
// 返回 std::optional<User>,明确表达可能找不到用户
std::optional<User> findUserById(int id) const {
auto it = _users.find(id);
if (it != _users.end()) {
return it->second; // 自动隐式转换为 std::optional<User>
}
return std::nullopt; // 明确返回无值
}
};

int main() {
UserRepository repo;

// 案例 1:用户存在
auto user1 = repo.findUserById(1);
if (user1) {
std::cout << "Found user: " << user1->name << "\n";
}

// 案例 2:用户不存在,使用 value_or 提供默认行为
auto user3 = repo.findUserById(3);
std::cout << "User 3 name: " << user3.value_or(User{0, "Anonymous"}).name << "\n";

return 0;
}

四、注意

  • std::optional 的大小通常等于 sizeof(T) + sizeof(bool),外加因内存对齐(alignment)产生的填充字节(padding)
  • 尽管 std::optional 很棒,但不要在性能极其苛刻的循环(如每秒数百万次调用的核心渲染循环)中滥用,因为多出来的那个 bool 标志和分支预测可能会带来微小的开销
  • 避免将 std::optional 作为函数形参,这会造成不必要的拷贝。如果参数可选,通常使用重载函数或传入默认值更合适。它的最佳作用是作为返回值
  • std::optional 不支持直接保存引用。如果你需要可选的引用,应该使用指针 T*(最简单),或者使用 std::optional>

C++ std::optional
http://example.com/2026/06/01/C++-std-optional/
作者
Yu xin
发布于
2026年6月1日
许可协议