深度剖析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,能够显著提升代码的质量和可靠性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/8363.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

云计算架构学习之LNMP架构部署、架构拆分、负载均衡-会话保持

一.LNMP架构部署 1.1. LNMP服务搭建 1.磁盘信息 2.内存 3.负载信息 4.Nginx你们公司都用来干嘛 5.文件句柄(文件描述符 打开文件最大数量) 6.你处理过系统中的漏洞吗 SSH漏洞 7.你写过什么shell脚本 8.监控通过什么告警 zabbix 具体监控哪些内容 9.mysql redis查询 你好H…

[BSidesCF 2020]Had a bad day1

题目 这里有传参 文件包含使用伪协议读取flag 先读取index.php查看 /index.php?categoryphp://filter/readconvert.base64-encode/resourceindex 解码 index.php源码 <?php$file $_GET[category];if(isset($file)){if( strpos( $file, "woofers" ) ! false …

12 款开源OCR发 PDF 识别框架

2024 年 12 款开源文档解析框架的选型对比评测&#xff1a;PDF解析、OCR识别功能解读、应用场景分析及优缺点比较 这是该系列的第二篇文章&#xff0c;聚焦于智能文档处理&#xff08;特别是 PDF 解析&#xff09;。无论是在模型预训练的数据收集阶段&#xff0c;还是基于 RAG…

银行卡三要素验证接口:方便快捷地实现银行卡核验功能

银行卡三要素验证API&#xff1a;防止欺诈交易的有力武器 随着互联网的发展&#xff0c;电子支付方式也越来越普及。在支付过程中&#xff0c;银行卡是最常用的支付工具之一。然而&#xff0c;在一些支付场景中&#xff0c;需要对用户的银行卡信息进行验证&#xff0c;以确保支…

Lite.Ai.ToolKit - 一个轻量级的 C++ 工具包

&#x1f6e0;**Lite.Ai.ToolKit**&#xff1a;一个轻量级的 C 工具包&#xff0c;包含 100 个很棒的 AI 模型&#xff0c;例如对象检测、人脸检测、人脸识别、分割、遮罩等。请参阅 Model Zoo 和 ONNX Hub、MNN Hub、TNN Hub、NCNN Hub。 3700 Stars 711 Forks 0 Issues 6 贡献…

node.js 07.npm下包慢的问题与nrm的使用

一.npm下包慢 因为npm i 默认从npm官网服务器进行下包,但是npm官网服务器是海外服务器所以响应很慢. 于是我们通过npm下包的时候通常用淘宝镜像进行下包,下面是切换到淘宝镜像地址下包的操作. 二.nrm的使用 nrm是一个管理切换npm下包地址的工具,可以快速切换下包的地址. 安…

读书笔记--分布式服务架构对比及优势

本篇是在上一篇的基础上&#xff0c;主要对共享服务平台建设所依赖的分布式服务架构进行学习&#xff0c;主要记录和思考如下&#xff0c;供大家学习参考。随着企业各业务数字化转型工作的推进&#xff0c;之前在传统的单一系统&#xff08;或单体应用&#xff09;模式中&#…

基于ADS的电感和变压器的建模过程

1. 电感二端口建模 对于固定尺寸单圈电感&#xff0c;从0.5G-200GHz的仿真&#xff0c;并提取其模型 如果想要在50GHz前把模型建准&#xff0c;仿真可能要建到200G&#xff0c;因为需要高频的数据&#xff0c;频率越高信息也越多。首先要调用文件由于数据是存在一个文件夹里面的…

使用Maxscript定义纹理贴图的方法

在3ds Max中,MaxScript 是一种用于插件编写和自动化任务的强大工具。通过MaxScript,你可以创建和操作对象、材质、灯光等等。要为材质分配纹理贴图,你可以按照以下方法来编写脚本。直接代码: myBmp = bitmaptexture filename:"D:\map001.tga" meditmaterials[1]…

初阶数据结构:链表(二)

目录 一、前言 二、带头双向循环链表 1.带头双向循环链表的结构 &#xff08;1)什么是带头&#xff1f; (2)什么是双向呢&#xff1f; &#xff08;3&#xff09;那什么是循环呢&#xff1f; 2.带头双向循环链表的实现 &#xff08;1&#xff09;节点结构 &#xff08;2…

项目开发实践——基于SpringBoot+Vue3实现的在线考试系统(九)(完结篇)

文章目录 一、成绩查询模块实现1、学生成绩查询功能实现1.1 页面设计1.2 前端页面实现1.3 后端功能实现2、成绩分段查询功能实现2.1 页面设计2.2 前端页面实现2.3 后端功能实现二、试卷练习模块实现三、我的分数模块实现1、 页面设计2、 前端页面实现3、 后端功能实现四、交流区…

环境搭建--vscode

vscode官网下载合适版本 安装vscode插件 安装 MinGW 配置环境变量 把安装目录D&#xff1a;\mingw64 配置在用户的环境变量path里即可 选择用户环境变量path 点确定保存后开启cmd输入g&#xff0c;如提示no input files 则说明Mingw64 安装成功&#xff0c;如果提示g 不是内…

爱的魔力转圈圈,基于carsim与simulink模拟仰望u8原地调头

仰望U8原地转向的示意图如下&#xff0c;不动方向盘的情况下&#xff0c;车可以自己转圈圈&#xff1a; 原理也很简单&#xff0c;仰望u8是四轮驱动&#xff0c;四个轮子都单独由四个轮边电机驱动。主要我们将左右的车轮轮速控制成左右两边轮速相同&#xff0c;但是方向相反&am…

1.1第1章DC/DC变换器的动态建模-1.1状态平均的概念--电力电子系统建模及控制 (徐德鸿)--读书笔记

电力电子系统一般由电力电子变换器&#xff08;滤波电路和开关&#xff09;、PWM 调制器、驱动电路、反馈控制单元构成&#xff0c;如图1-1所示。由控制理论的知识&#xff0c;电力电子系统的静态和动态性能的好坏与反馈控制设计密切相关。要进行反馈控制设计&#xff0c;首先要…

6. 使用springboot做一个音乐播放器软件项目【1.0版项目完结】附带源码~

#万物OOP 注意&#xff1a; 本项目只实现播放音乐和后台管理系统。 不分享任何音乐歌曲资源。 上一篇文章我们 做了音乐播放器后台的功能。参考地址&#xff1a; https://jsonll.blog.csdn.net/article/details/145214363 这个项目已经好几天也没更新了&#xff0c;因为临近放…

WGCLOUD使用介绍 - 如何监控ActiveMQ和RabbitMQ

根据WGCLOUD官网的信息&#xff0c;目前没有针对ActiveMQ和RabbitMQ这两个组件专门做适配 不过可以使用WGCLOUD已经具备的通用监测模块&#xff1a;进程监测、端口监测或者日志监测、接口监测 来对这两个组件进行监控

豆包MarsCode:字符串字符类型排序问题

问题描述 思路分析 我们需要对字符串中的字母、数字、问号按照规则进行排序&#xff0c;具体要求是&#xff1a; 问号的位置不变。数字的位置不变&#xff0c;但数字之间要按照从大到小排序。字母的位置不变&#xff0c;但字母之间要按照字典序排序。 解决此问题的思路分为以…

[STM32 标准库]定时器输出PWM配置流程 PWM模式解析

前言&#xff1a; 本文内容基本来自江协&#xff0c;整理起来方便日后开发使用。MCU&#xff1a;STM32F103C8T6。 一、配置流程 1、开启GPIO&#xff0c;TIM的时钟 /*开启时钟*/RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟RCC_APB2PeriphClockC…

无人机红外热成像:应急消防的“透视眼”

无人机红外热成像&#xff1a;应急消防的“透视眼” 亲爱的小伙伴们&#xff0c;每年一到夏天&#xff0c;应急消防的战士们就像上紧了发条的闹钟&#xff0c;时刻准备应对各种灾害。炎热天气让火灾隐患“蹭蹭”往上涨&#xff0c;南北各地还有防洪救灾、台风、泥石流等灾害轮…

【Redis】常见面试题

什么是Redis&#xff1f; Redis 和 Memcached 有什么区别&#xff1f; 为什么用 Redis 作为 MySQL 的缓存&#xff1f; 主要是因为Redis具备高性能和高并发两种特性。 高性能&#xff1a;MySQL中数据是从磁盘读取的&#xff0c;而Redis是直接操作内存&#xff0c;速度相当快…