一、基本概念
C++17 引入了 std::optional,表示一个可能有值的对象(没有值时就是默认的 std::nullopt)。它提供了一种类型安全的方式来表示可能存在也可能不存在的值。
在 std::optional 出现之前,我们通常使用魔术值(如 -1、nullptr、空字符串)或通过指针/引用参数来表达“无值”的概念。这些方法要么不安全,要么语义模糊。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() { std::optional<int> emptyOpt; std::optional<int> emptyOpt2 = std::nullopt;
std::optional<int> valuedOpt = 42; std::optional<std::string> stringOpt{"Hello World"};
auto floatOpt = std::make_optional<float>(3.14f); 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) { if (opt.has_value()) { std::cout << "Value: " << *opt << std::endl; }
try { std::cout << "Value: " << opt.value() << std::endl; } catch (const std::bad_optional_access& e) { std::cerr << "Error: " << e.what() << std::endl; }
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> findUserById(int id) const { auto it = _users.find(id); if (it != _users.end()) { return it->second; } return std::nullopt; } };
int main() { UserRepository repo;
auto user1 = repo.findUserById(1); if (user1) { std::cout << "Found user: " << user1->name << "\n"; }
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>