当前位置: 首页 > article >正文

深度剖析C++17中的std::optional:处理可能缺失值的利器

生成特定比例的图片.png

文章目录

    • 一、基本概念与设计理念
    • 二、构建与初始化
      • (一)默认构造
      • (二)值初始化
      • (三)使用`std::make_optional`
      • (四)使用`std::nullopt`
    • 三、访问值
      • (一)`value()`
      • (二)`value_or(T default_value)`
      • (三)使用解引用操作符`*`和`->`
    • 四、修改器
      • (一)`reset()`
      • (二)`emplace()`
      • (三)`operator=`
    • 五、观察器
      • (一)`has_value()`
      • (二)`operator bool()`
      • (三)`value_type`
    • 六、使用场景
      • (一)函数返回值
      • (二)容器元素
      • (三)避免空指针异常
    • 七、性能考虑
    • 八、总结

在C++17的标准库中, std::optional是一个极为实用的工具,它为处理可能缺失的值提供了一种安全、高效且直观的方式。在传统的C++编程里,处理可能不存在的值是一个棘手的问题,通常依赖于特殊标记值,比如指针设为 nullptr,整数设为 -1 ,或者浮点数设为 std::numeric_limits<T>::quiet_NaN()等。但这些方式容易引入潜在的错误,尤其是当特殊标记值与合法数据值冲突时,还会让代码逻辑变得复杂难读。 std::optional的出现,很好地解决了这些痛点。

一、基本概念与设计理念

std::optional是C++17引入的一个模板类,它的设计目的是清晰地表达一个值可能存在,也可能不存在的情况。从本质上来说,std::optional是一个包含了一个值或者什么都不包含的对象。它通过将值的存在性和值本身封装在一起,使得代码能够更明确地处理可能缺失值的场景,提升了代码的安全性和可读性。

例如,考虑一个从数据库中获取用户年龄的函数。在某些情况下,数据库中可能没有记录该用户的年龄信息。使用std::optional,可以这样编写代码:

#include <optional>// 假设这是从数据库获取年龄的函数
std::optional<int> getAgeFromDatabase(int userId) {// 这里模拟数据库查询逻辑,假设某些情况下没有年龄数据if (userId == 1) {return 30;} else {return std::nullopt;}
}int main() {auto age = getAgeFromDatabase(2);if (age.has_value()) {std::cout << "用户年龄是: " << age.value() << std::endl;} else {std::cout << "未找到该用户的年龄信息" << std::endl;}return 0;
}

在这个例子中,getAgeFromDatabase函数返回一个std::optional<int>,如果找到了年龄,就返回包含年龄值的std::optional;如果没找到,就返回std::nullopt,表示没有值。在main函数中,通过has_value方法检查是否有值,再进行相应的处理,逻辑非常清晰。

二、构建与初始化

(一)默认构造

std::optional可以进行默认构造,此时它处于空状态,即不包含任何值:

std::optional<int> opt1; // 空的std::optional

(二)值初始化

通过直接赋值的方式,可以将一个值初始化为std::optional

std::optional<std::string> opt2 = "Hello, optional";

(三)使用std::make_optional

std::make_optional是一个便捷的函数模板,用于创建std::optional对象。它会直接在内部构造值,避免了不必要的拷贝或移动操作,在性能上更有优势:

auto opt3 = std::make_optional<std::vector<int>>({1, 2, 3});

(四)使用std::nullopt

std::nullopt是一个特殊的常量,专门用于表示std::optional为空的状态。可以在初始化时显式使用它来表明std::optional不包含值:

std::optional<double> opt4 = std::nullopt;

三、访问值

(一)value()

value()方法用于获取std::optional中存储的值。但需要特别注意的是,如果std::optional为空,调用value()会抛出std::bad_optional_access异常。所以在调用value()之前,务必先使用has_value()方法检查std::optional是否包含值:

std::optional<int> opt = 42;
if (opt.has_value()) {int val = opt.value();std::cout << "值是: " << val << std::endl;
}

(二)value_or(T default_value)

value_or方法提供了一种更安全的取值方式。当std::optional包含值时,它返回该值;当std::optional为空时,它返回传入的默认值。这样就避免了因调用value()方法在空状态下抛出异常的风险:

std::optional<int> opt5;
int result = opt5.value_or(100); // result为100

(三)使用解引用操作符*->

std::optional重载了*->操作符,当std::optional包含值时,可以像使用普通指针一样访问值。*操作符返回值的引用,->操作符用于访问值内部的成员:

class MyClass {
public:void print() {std::cout << "这是MyClass的实例" << std::endl;}
};std::optional<MyClass> opt6;
opt6.emplace();
if (opt6) {opt6->print(); // 调用MyClass的print方法(*opt6).print(); // 与上面的效果相同
}

四、修改器

(一)reset()

reset()方法用于将std::optional设置为空状态,即移除其中存储的值。之后再调用has_value()方法会返回false

std::optional<int> opt7 = 42;
opt7.reset();
if (!opt7.has_value()) {std::cout << "opt7现在为空" << std::endl;
}

(二)emplace()

emplace()方法允许在std::optional内部直接构造值,而不需要先移除旧值再进行赋值。这在构造复杂对象时非常有用,可以避免不必要的构造和析构开销,提高效率:

std::optional<std::string> opt8;
opt8.emplace("新的值");

(三)operator=

可以使用赋值操作符=来修改std::optional的值。如果std::optional之前为空,赋值后会包含新值;如果之前有值,会先销毁旧值,再存储新值:

std::optional<int> opt9 = 10;
opt9 = 20;

五、观察器

(一)has_value()

has_value()方法是最常用的观察器之一,用于检查std::optional是否包含值。在访问std::optional中的值之前,通常会先调用这个方法进行检查:

std::optional<double> opt10;
if (opt10.has_value()) {std::cout << "opt10有值" << std::endl;
} else {std::cout << "opt10为空" << std::endl;
}

(二)operator bool()

std::optional重载了bool类型转换操作符,使得可以直接在条件语句中判断std::optional是否包含值。这种方式简洁明了,常用于简化代码逻辑:

std::optional<std::vector<int>> opt11 = {1, 2, 3};
if (opt11) {std::cout << "opt11包含一个非空的vector" << std::endl;
}

(三)value_type

value_typestd::optional的嵌套类型别名,用于获取存储值的类型。在一些需要使用类型信息的模板编程场景中非常有用:

std::optional<std::string> opt12;
using value_type = std::optional<std::string>::value_type;

六、使用场景

(一)函数返回值

在函数返回值可能缺失的情况下,std::optional能清晰地表达这种不确定性。比如在实现一个查找元素索引的函数时:

std::optional<size_t> findIndex(const std::vector<int>& vec, int target) {for (size_t i = 0; i < vec.size(); ++i) {if (vec[i] == target) {return i;}}return std::nullopt;
}

(二)容器元素

std::optional可以作为容器的元素,用于表示容器中可能存在缺失值的情况。例如,在一个记录学生成绩的std::vector中,某些学生可能缺考:

std::vector<std::optional<int>> scores(10);
scores[3] = 85; // 学生3的成绩

(三)避免空指针异常

在使用指针的场景中,std::optional可以替代指针来避免空指针异常。例如,在管理动态分配对象的生命周期时:

std::optional<std::unique_ptr<MyClass>> obj;
if (someCondition) {obj.emplace(std::make_unique<MyClass>());
}
if (obj) {obj->doSomething();
}

七、性能考虑

从性能角度来看,std::optional的实现是非常高效的。在大多数情况下,它的内存占用只比存储的值多一个布尔标志位,用于表示值是否存在。这意味着在空间复杂度上,std::optional的额外开销极小。

在时间复杂度方面,std::optional的构造和析构操作与普通对象的开销相当。emplace方法更是直接在内部构造值,避免了不必要的拷贝和移动操作,进一步提高了效率。不过,在频繁进行值的存在性检查和访问操作时,由于需要额外的条件判断,可能会对性能产生一定的影响。但总体而言,与传统的使用特殊标记值来处理可能缺失值的方式相比,std::optional在性能和安全性上都有显著的优势。

八、总结

std::optional是C++17标准库中一个极具价值的特性,它为C++开发者提供了一种强大的工具,用于处理可能缺失值的情况。通过清晰地表达值的存在性,std::optional使得代码更易于理解和维护,同时减少了因处理缺失值不当而引发的错误。无论是在函数返回值、容器元素,还是在避免空指针异常等场景中,std::optional都展现出了其独特的优势。在实际的C++17项目开发中,合理运用std::optional,能够显著提升代码的质量和可靠性。

相关文章:

深度剖析C++17中的std::optional:处理可能缺失值的利器

文章目录 一、基本概念与设计理念二、构建与初始化&#xff08;一&#xff09;默认构造&#xff08;二&#xff09;值初始化&#xff08;三&#xff09;使用std::make_optional&#xff08;四&#xff09;使用std::nullopt 三、访问值&#xff08;一&#xff09;value()&#x…...

MySQL用户授权、收回权限与查看权限

【图书推荐】《MySQL 9从入门到性能优化&#xff08;视频教学版&#xff09;》-CSDN博客 《MySQL 9从入门到性能优化&#xff08;视频教学版&#xff09;&#xff08;数据库技术丛书&#xff09;》(王英英)【摘要 书评 试读】- 京东图书 (jd.com) MySQL9数据库技术_夏天又到了…...

每日一题 429. N 叉树的层序遍历

429. N 叉树的层序遍历 /*class Solution { public:vector<vector<int>> levelOrder(Node* root) {queue<Node*> que;que.push(root);vector<vector<int>> ans;if(root nullptr){return ans;}while(!que.empty()){int sizeQue que.size();vec…...

【Maui】注销用户,采用“手势”点击label弹窗选择

文章目录 前言一、问题描述二、解决方案三、软件开发&#xff08;源码&#xff09;3.1 方法一&#xff1a;前端绑定3.2 方法二&#xff1a;后端绑定3.3 注销用户的方法 四、项目展示 前言 .NET 多平台应用 UI (.NET MAUI) 是一个跨平台框架&#xff0c;用于使用 C# 和 XAML 创…...

如何将xps文件转换为txt文件?xps转为pdf,pdf转为txt,提取pdf表格并转为txt

文章目录 xps转txt方法一方法二 pdf转txt整页转txt提取pdf表格&#xff0c;并转为txt 总结另外参考XPS文件转换为TXT文件XPS文件转换为PDF文件PDF文件转换为TXT文件提取PDF表格并转为TXT示例代码&#xff08;部分&#xff09; 本文测试代码已上传&#xff0c;路径如下&#xff…...

Object类(2)

大家好&#xff0c;今天我们继续来看看Object类中一些成员方法&#xff0c;这些方法在实际中有很大的用处&#xff0c;话不多说&#xff0c;来看。 注&#xff1a;所有类都默认继承Object类的&#xff0c;所以可调用Object类中的方法&#xff0c;如equals&#xff0c;也可以发生…...

BGP分解实验·11——路由聚合与条件性通告(3)

续接上&#xff08;2&#xff09;的实验。其拓扑如下&#xff1a; 路由聚合的负向也就是拆分&#xff0c;在有双出口的情况下&#xff0c;在多出口做流量分担是优选方法之一。 BGP可以根据指定来源而聚合路由&#xff0c;在产生该聚合路由的范围内的条目注入到本地BGP表后再向…...

无用的知识又增加了:is_assignable means?

std::pair的默认operator被delete掉了&#xff0c;取而代之的是两个enable_if版本。 为什么这么设计&#xff0c;我的理解是在std::map里&#xff0c;已经保存的元素的key值是不能被修改的&#xff0c;比如 注意&#xff0c;下面的代码会修改key值&#xff0c;编译时出现错误…...

MOS的体二极管能通多大电流

第一个问题&#xff1a;MOS导通之后电流方向可以使任意的&#xff0c;既可以从D到S&#xff0c;也可以从S到D。 第二个问题&#xff1a;MOS里面的体二极管电流可以达到几百安培&#xff0c;这也就解释了MOS选型的时候很少考虑体二极管的最大电流&#xff0c;而是考虑DS之间电流…...

C语言【基础篇】之流程控制——掌握三大结构的奥秘

流程控制 &#x1f680;前言&#x1f99c;顺序结构&#x1f4af; 定义&#x1f4af;执行规则 &#x1f31f;选择结构&#x1f4af;if语句&#x1f4af;switch语句&#x1f4af;case穿透规则 &#x1f914;循环结构&#x1f4af;for循环&#x1f4af;while循环&#x1f4af;do -…...

Node.js下载安装及环境配置教程 (详细版)

Node.js&#xff1a;是一个基于 Chrome V8 引擎的 JavaScript 运行时&#xff0c;用于构建可扩展的网络应用程序。Node.js 使用事件驱动、非阻塞 I/O 模型&#xff0c;使其非常适合构建实时应用程序。 Node.js 提供了一种轻量、高效、可扩展的方式来构建网络应用程序&#xff0…...

【PySide6拓展】QCalendarWidget 日历控件

文章目录 PySide6拓展&#xff1a;QCalendarWidget 日历控件前言什么是QCalendarWidget&#xff1f;特点&#xff1a; 如何使用QCalendarWidget&#xff1f;1. 基本用法2. 获取当前选中的日期 QCalendarWidget 常用函数分类介绍1. 日期选择与管理函数2. 日期格式与显示函数3. 日…...

新型人工智能“黑帽”工具:GhostGPT带来的威胁与挑战

生成式人工智能的发展既带来了有益的生产力转型机会&#xff0c;也提供了被恶意利用的机会。 最近&#xff0c;Abnormal Security的研究人员发现了一个专门为网络犯罪创建的无审查AI聊天机器人——GhostGPT&#xff0c;是人工智能用于非法活动的新前沿&#xff0c;可以被用于网…...

【文星索引】搜索引擎项目测试报告

目录 一、项目背景二、 项目功能2.1 数据收集与索引2.2 API搜索功能2.3 用户体验与界面设计2.4 性能优化与维护 三、测试报告3.1 功能测试3.2 界面测试3.3 性能测试3.4 兼容性测试3.5 自动化测试 四、测试总结4.1 功能测试方面4.2 性能测试方面4.3 用户界面测试方面 一、项目背…...

【PostgreSQL内核学习 —— (WindowAgg(一))】

WindowAgg 窗口函数介绍WindowAgg理论层面源码层面WindowObjectData 结构体WindowStatePerFuncData 结构体WindowStatePerAggData 结构体eval_windowaggregates 函数update_frameheadpos 函数 声明&#xff1a;本文的部分内容参考了他人的文章。在编写过程中&#xff0c;我们尊…...

Maya快捷键

旋转 ALT鼠标左键 平移 ALT鼠标中键 缩放 ALT鼠标右键 / 滑动鼠标滚轮 切换视图 空格键 [Q] 选择状态 [W] 移动状态 [E] 旋转状态 [R] 绽放状态 1正常显示 2正常也圆滑同时显示 3圆滑显示 4线框显示 5材质显示 6贴图显示 7灯光显示 CTRLZ 撤销命令…...

Go反射指南

概念&#xff1a; 官方对此有个非常简明的介绍&#xff0c;两句话耐人寻味&#xff1a; 反射提供一种让程序检查自身结构的能力反射是困惑的源泉 第1条&#xff0c;再精确点的描述是“反射是一种检查interface变量的底层类型和值的机制”。 第2条&#xff0c;很有喜感的自嘲…...

深入理解 C 语言函数指针的高级用法:(void (*) (void *)) _IO_funlockfile

深入理解 C 语言函数指针的高级用法 函数指针是 C 语言中极具威力的特性&#xff0c;广泛用于实现回调、动态函数调用以及灵活的程序设计。然而&#xff0c;复杂的函数指针声明常常让即使是有经验的开发者也感到困惑。本文将从函数指针的基本概念出发&#xff0c;逐步解析复杂…...

【观察】甲骨文:以AI为中心,开启企业级平台架构“新革命”

知名科技杂志《连线》创始主编凯文凯利曾预测&#xff1a;“在未来的 100 年里&#xff0c;人工智能将超越任何一种人工力量&#xff0c;将人类引领到一个前所未有的时代。” 可以看到&#xff0c;随着近两年AIGC与大模型的快速爆发&#xff0c;AI在千万行业中的重要性愈发突出…...

react native在windows环境搭建并使用脚手架新建工程

截止到2024-1-11&#xff0c;使用的主要软件的版本如下&#xff1a; 软件实体版本react-native0.77.0react18.3.1react-native-community/cli15.0.1Android Studio2022.3.1 Patch3Android SDKAndroid SDK Platform 34 35Android SDKAndroid SDK Tools 34 35Android SDKIntel x…...

C语言从入门到进阶

视频&#xff1a;https://www.bilibili.com/video/BV1Vm4y1r7jY?spm_id_from333.788.player.switch&vd_sourcec988f28ad9af37435316731758625407&p23 //枚举常量 enum Sex{MALE,FEMALE,SECRET };printf("%d\n", MALE);//0 printf("%d\n", FEMALE…...

Python案例--养兔子

兔子繁殖问题是一个经典的数学问题&#xff0c;最早由意大利数学家斐波那契在13世纪提出。这个问题不仅在数学领域具有重要意义&#xff0c;还广泛应用于计算机科学、生物学和经济学等领域。本文将通过一个具体的Python程序&#xff0c;深入探讨兔子繁殖问题的建模和实现&#…...

Mybatis——sql映射文件中的增删查改

映射文件内的增删查改 准备工作 准备一张数据表&#xff0c;用于进行数据库的相关操作。新建maven工程&#xff0c; 导入mysql-connector-java和mybatis依赖。新建一个实体类&#xff0c;类的字段要和数据表的数据对应编写接口编写mybatis主配置文件 public class User {priva…...

goframe 博客分类文章模型文档 主要解决关联

goframe 博客文章模型文档 模型结构 (BlogArticleInfoRes) BlogArticleInfoRes 结构体代表系统中的一篇博客文章&#xff0c;包含完整的元数据和内容管理功能。 type BlogArticleInfoRes struct {Id uint orm:"id,primary" json:"id" …...

人工智能在医疗领域的应用有哪些?

人工智能在医疗领域的应用十分广泛&#xff0c;涵盖了诊断、治疗、药物研发等多个环节&#xff0c;以下是一些主要的应用&#xff1a; 医疗影像诊断 疾病识别&#xff1a;通过分析 X 光、CT、MRI 等影像&#xff0c;人工智能算法能够识别出肿瘤、结节、骨折等病变&#xff0c;…...

学习第七十六行

提高github下载速度方法 1.github转码云 2.https://github.com.cnpmjs.org com后面加东西 对于面试笔试&#xff0c;最好方法刷力扣&#xff0c;1000题包进大厂的...

C#System.Threading.Timer定时器意外回收注意事项

System.Threading.Timer定时器使用时会出现意外回收的情况。具体解释如下: 只要在使用 Timer,就必须保留对它的引用。对于任何托管对象,如果没有对 Timer 的引用,计时器会被垃圾回收。即使 Timer 仍处在活动状态,也会被回收。 实例对比测试 实例 定义两个类,其中一个…...

若依基本使用及改造记录

若依框架想必大家都了解得不少&#xff0c;不可否认这是一款及其简便易用的框架。 在某种情况下&#xff08;比如私活&#xff09;使用起来可谓是快得一匹。 在这里小兵结合自身实际使用情况&#xff0c;记录一下我对若依框架的使用和改造情况。 一、源码下载 前往码云进行…...

Java基础教程(007):方法的重载与方法的练习

文章目录 6.5 方法的重载6.6 方法练习数组遍历数组最大值 6.5 方法的重载 在 Java 中&#xff0c;方法的重载是指在同一个类中定义多个方法&#xff0c;这些方法具有相同的名称&#xff0c;但参数列表不同。方法的重载是一种实现多态的方式&#xff0c;允许一个方法名以不同的…...

Day27-【13003】短文,线性表两种基本实现方式空间效率、时间效率比较?兼顾优点的静态链表是什么?如何融入空闲单元链表来解决问题?

文章目录 本次内容总览第四节&#xff0c;两种基本实现方式概览两种基本实现方式的比较元素个数n大于多少时&#xff0c;使用顺序表存储的空间效率才会更高&#xff1f;时间效率比较&#xff1f;*、访问操作&#xff0c;也就是读运算&#xff0c;读操作1、插入&#xff0c;2、删…...