C++新特性整理目录:
std::optional简要说明

std::optional 是 C++17 引入的一种类模板,用于表示一个值可能存在也可能不存在的情况。它是一个轻量级的包装器,能够显式表达“可选值”的语义,避免使用特殊值(如 -1 或 nullptr)来表示无效状态,从而提高代码的可读性和安全性。
简单的说,std::optional主要是用来解决“无值”表示的问题,即C++ 程序员如何表示一个“可能没有值”的语义?
我们先来思考一下,什么情况下会需要表示一个“可能没有值”的语义呢?
(1)查找和搜索操作:当在集合中查找元素时,可能找不到目标
(2)解析和转换:将字符串解析为其他类型时,字符串可能不符合格式要求。
(3)配置和可选参数:某些配置项可能是可选的。
(4)函数可能失败的计算:某些数学运算在特定条件下无意义
(5)获取序列中的最大值,但序列可能为空。
等等。也许有的人在写程序时,并没有太过在意这些问题,有的人可能在这里纠结了半天:我该如何表示没有值的情况,没有值的情况下,我的函数该返回什么才能保证编译通过,才能在调用时能够知道当前是否有值或者是有效值?
那么,在 std::optional 出现之前,我们如何表示一个“可能没有值”的语义呢?
我们一般会这样做:即使用特殊值:例如用 -1 表示找不到的索引,用 nullptr 表示空指针,用 std::string::npos 表示未找到的位置,在自定义的类中使用valid变量记录该对象是否有效等等。这确实可以解决上面的问题,但是他们却都有一个很严重的问题:“它们都没有在类型系统中明确地表达“可能无值”这一意图,容易导致运行时错误”,这是语义上的缺陷
示例代码如下:

// 使用特殊值-1表示无值情况
// 所有val都大于等于0,-1表示没有找到
int find_val_by_id(const std::map<std::string, int>& db, std::string id) {
    auto it = db.find(id);
    if (it != db.end()) return it->second;
    return -1;
}

// 使用特殊值nullptr表示无值情况
// nullptr表示没有找到
int* find_ptr_by_id(const std::map<std::string, int*>& db, std::string id) {
    auto it = db.find(id);
    if (it != db.end()) return it->second;
    return nullptr;
}

class MyClass
{
public:
    MyClass() {};
    ~MyClass() {};
    void setValid(bool v) { mIsValid = v; };
    bool isValid() { return mIsValid; };
private:
    bool mIsValid = false;
};

// 使用特殊值mIsValid=false表示无值情况
MyClass getMyClassObject(const std::string& input)
{
    // 实现具体逻辑
    MyClass obj;
    if (true)
    {
        obj.setValid(true);
    }
    return obj;
}

// 使用特殊值nullptr表示无值情况
MyClass* getMyClassObjectPtr(const std::string& input)
{
    if (true)
    {
        return new MyClass();
    }
    return nullptr;
}
int main()
{
   
    std::map<std::string, int> db1;
    int r1 = find_val_by_id(db1, "001");
    if (r1 != -1) // 思考一个问题,如果这个函数不是你实现的,你怎么知道要做设个判断?
    {
        // do some thing
    }

    std::map<std::string, int*> db2;
    int* r2 = find_ptr_by_id(db2, "001");
    if (r2 != nullptr)  // 思考一个问题,如果这个函数不是你实现的,你怎么知道要做设个判断?
    {
        // do some thing
    }

    MyClass obj1 = getMyClassObject("obj1");
    if (obj1.isValid())  // 思考一个问题,如果这个函数不是你实现的,你怎么知道要做设个判断?
    {
        // do some thing
    }

    MyClass* objptr = getMyClassObjectPtr("objptr");
    if (objptr != nullptr)  // 思考一个问题,如果这个函数不是你实现的,你怎么知道要做设个判断?
    {
        // do some thing
    }
}

从上面的示例代码中,你能看出哪些函数存在可能没有值的情况吗?如果光从定义看,我们好像确实没办法确定。如果这些函数接口是我们自己用还好,因为我们自己了解函数输出情况,如果是给别人用,那就糟糕透了。
如果别人没有做无效值判断、没有做空值判断,想想这会是一件多么糟糕的事情?然儿对于函数使用者来说,我不知道有这种情况,我为什么要去判断?
其实判断空指针已经成为大家的一个共识、习惯了,所以这还好,但是如果我必须返回一个对象呢???而且使用指针也会引出一个新的问题:动态分配,这是一个效率问题;指针由谁释放,这是资源管理的问题。
那么使用std::optional后会怎么样呢,由于std::optional是一个明确的标准,是大家都知道的。我使用了std::optional就是要告诉你,我的函数存在“可能没有值”的情况,你在使用时要注意判断,如果你还是没有判断,那就怪不得我了,因为我在语义已经告诉你了,你不听,又怎能怪我呢,对吧?下面是使用std::optional的示例代码:

// 在map中查找键
std::optional<std::string> find_name_by_id(const std::map<int, std::string>& db, int id) {
   auto it = db.find(id);
   if (it != db.end()) return it->second;
   return std::nullopt;
}

// 在vector中查找满足条件的元素
std::optional<int> find_first_even(const std::vector<int>& vec) {
   for (int num : vec) {
       if (num % 2 == 0) return num;
   }
   return std::nullopt;
}

// 在vector中查找满足条件的元素
std::optional<MyClass> getMyClassObject(const std::vector<MyClass>& vec,int index) {
   if (index >= 0 && index < vec.size())
       return vec[index];
   return std::nullopt;
}

说了这么多,那么使用std::optional的主要价值有哪些呢?

  • 表达意图明确 - 清楚地表示"可能有值,可能没有"
  • 类型安全 - 强制调用者处理"无值"的情况
  • 性能优秀 - 通常实现为值语义,无动态分配开销
  • API清晰 - 使接口设计更加自解释
std::optional 基本使用

1、基本声明
这很简单,代码如下

	// 基本声明
	std::optional<int> opt_int;
	std::optional<std::string> opt_string;
	std::optional<double> opt_double = std::nullopt;

2、创建和初始化
有直接初始化和使用std::make_optional两种方式,示例代码如下:

	// 直接初始化
	// 包含值的情况
	std::optional<int> opt1 = 42;           // 从值初始化
	std::optional<int> opt2{ 100 };           // 直接初始化
	std::optional<std::string> opt3 = "hello"; // 字符串

	// 空值情况
	std::optional<int> opt4 = std::nullopt; // 明确为空
	std::optional<int> opt5;                // 默认构造为空

	// 使用 std::make_optional
	auto opt6 = std::make_optional(42);           // 自动推导类型
	auto opt7 = std::make_optional("hello");
	//auto opt8 = std::make_optional({ 1, 2, 3 }); //无法推导出类型
	auto opt8 = std::make_optional<std::vector<int>>({ 1, 2, 3 });
	auto opt9 = std::make_optional(std::vector<int>{ 1, 2, 3 });

3、检查是否有值

   std::optional<int> opt = 42;

   // 多种检查方式
   if (opt.has_value()) {
       std::cout << "有值\n";
   }

   if (opt) {  // 重载了 bool 转换
       std::cout << "有值\n";
   }

   if (!opt) {
       std::cout << "无值\n";
   }

4、访问值的方法

std::optional<std::string> opt = "hello";

// 1. value() - 安全访问,无值时抛异常
try {
    std::string s1 = opt.value();  // 返回 "hello"
}
catch (const std::bad_optional_access& e) {
    std::cout << "无值异常: " << e.what() << std::endl;
}

// 2. operator* - 直接访问,无值时未定义行为
if (opt) {
    std::string s2 = *opt;         // 解引用
    std::cout << s2 << std::endl;  // 输出 "hello"
}

// 3. operator-> - 访问成员
std::optional<std::string> opt_str = "test";
if (opt_str) {
    size_t len = opt_str->length();  // 相当于 (*opt_str).length()
    std::cout << "长度: " << len << std::endl;
}

5、安全获取值

   std::optional<int> opt;

   // value_or() - 提供默认值
   int value1 = opt.value_or(100);  // opt为空,返回100
   std::cout << value1 << std::endl; // 输出 100

   opt = 42;
   int value2 = opt.value_or(100);  // opt有值,返回42
   std::cout << value2 << std::endl; // 输出 42

6、修改值

 //赋值和重置
 std::optional<int> opt;

 // 赋值
 opt = 100;           // 现在包含 100
 opt = 200;           // 修改为 200
 opt = std::nullopt;  // 重置为空

 // reset() 方法
 opt = 300;
 opt.reset();         // 变为空

 // 原地构造 (emplace)
 std::optional<std::vector<int>> opt_vec;
 // 原地构造,避免拷贝
 opt_vec.emplace({ 1, 2, 3, 4, 5 });  // 直接构造vector
 // 等同于
 opt_vec = std::vector<int>{ 1, 2, 3, 4, 5 };

 // 交换sawp
 std::optional<int> a = 10;
 std::optional<int> b = 20;
 // 交换两个有值的 optional
 a.swap(b);
emplace与sawp函数功能分析

我们先自定义一个类,以便于后面的分析,类的实现如下:

class Point {
public:
   int x, y, z;
   std::string description;
   Point(int x, int y,int z = 10, std::string description = "unkonwn") : x(x), y(y), z(z) , >description(description){
       std::cout << "Point constructed: " << x << ", " << y << ", " << z << ", " << description << >std::endl;
   }
   Point(const Point& other) : x(other.x), y(other.y), z(other.z), description(other.description) {
       std::cout << "Point copied: " << x << ", " << y << ", " << z << ", " << description << std::endl;
   }
   Point& operator=(const Point& other) {
       x = other.x;
       y = other.y;
       z = other.z;
       description = other.description;
       std::cout << "Point assigned: " << x << ", " << y << ", " << z << ", " << description << std::endl;
       return *this;
   }
};

我们先来看看下面这样一段代码的运行结果吧:

   std::optional<Point> p1 = Point(10, 10, 10, "001");
   std::optional<Point> p2 = Point(20, 20, 20, "002");
   std::optional<Point> tp;
   tp = p1;
   p1 = p2;
   p2 = tp;

在这段代码中,我们首先创建了p1和p2,然后想交互一下p1、p2的值,我们来看看运行结果
在这里插入图片描述

执行了两次构造函数、三次拷贝构造函数、两次拷贝赋值运算。
我们再来看看使用sawp的运行结果:
代码如下:

std::optional<Point> p1 = Point(10, 10, 10, "001");
std::optional<Point> p2 = Point(20, 20, 20, "002");
p1.swap(p2);
std::cout << "p1:" << p1->description;
std::cout << "p2:" << p2->description;

运行结果如下:
在这里插入图片描述
依旧是执行了两次构造函数、三次拷贝构造函数、两次拷贝赋值运算。值也成果交换了
我们继续对代码进行修改,代码如下:

std::optional<Point> p1;
p1.emplace(10, 10, 10, "001");
std::optional<Point> p2;
p1.emplace(20, 20, 20, "002");
p1.swap(p2);
std::cout << "p1:" << p1->description << std::endl;
std::cout << "p2:" << p2->description << std::endl;

运行结果如下:
在这里插入图片描述
少了两次拷贝构造。

std::optional 的 Monadic 方法(C++23)
  • transform - 变换值:transform 对包含的值应用函数,返回新的 optional
#include <optional>
#include <string>
#include <iostream>

std::optional<int> to_int(const std::string& s) {
   try {
       return std::stoi(s);
   } catch (...) {
       return std::nullopt;
   }
}

int main() {
   std::optional<std::string> opt_str = "42";
   
   // 传统方式
   std::optional<int> old_way;
   if (opt_str.has_value()) {
       old_way = to_int(opt_str.value());
   }
   
   // 使用 transform (C++23)
   auto result = opt_str.transform([](const std::string& s) {
       return s + "!";
   }).transform([](const std::string& s) {
       return s.size();
   });
   
   // 如果 opt_str 有值: result 包含字符串长度
   // 如果 opt_str 为空: result 也为空
   
   std::cout << result.value_or(0) << std::endl; // 输出: 3 ("42!")
}
  • and_then - 链式返回 optional 的操作:and_then 类似于 transform,但要求函数返回 std::optional
#include <optional>
#include <string>
#include <iostream>
#include <charconv>

std::optional<int> parse_int(const std::string& s) {
   int value;
   auto [ptr, ec] = std::from_chars(s.data(), s.data() + s.size(), value);
   if (ec == std::errc() && ptr == s.data() + s.size()) {
       return value;
   }
   return std::nullopt;
}

std::optional<int> double_value(int x) {
   return x * 2;
}

int main() {
   std::optional<std::string> input = "123";
   
   // 传统方式需要多层嵌套检查
   auto old_result = [&]() -> std::optional<int> {
       if (!input.has_value()) return std::nullopt;
       auto parsed = parse_int(*input);
       if (!parsed.has_value()) return std::nullopt;
       return double_value(*parsed);
   }();
   
   // 使用 and_then 优雅链式处理
   auto result = input.and_then(parse_int)
                      .and_then(double_value);
   
   if (result) {
       std::cout << "Result: " << *result << std::endl; // 输出: 246
   }
}
  • or_else - 处理空值情况:or_else 在 optional 为空时调用函数,通常用于错误处理或提供回退
#include <optional>
#include <iostream>
#include <string>

std::optional<int> get_from_cache(const std::string& key) {
   std::cout << "Checking cache for: " << key << std::endl;
   return std::nullopt; // 模拟缓存未命中
}

std::optional<int> get_from_db(const std::string& key) {
   std::cout << "Querying database for: " << key << std::endl;
   return 42; // 模拟数据库有数据
}

int main() {
   std::string key = "user:123";
   
   // 传统方式
   auto value = get_from_cache(key);
   if (!value) {
       std::cout << "Cache miss, falling back to DB" << std::endl;
       value = get_from_db(key);
   }
   
   // 使用 or_else - 优雅的失败回退
   auto result = get_from_cache(key).or_else([&] {
      std::cout << "Cache miss, falling back to DB" << std::endl;
       return get_from_db(key);
  });
   
   if (result) {
       std::cout << "Final value: " << *result << std::endl;
   }
}
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐