std::optional与函数返回值的讨论

news/2024/11/2 0:03:13/

这个话题是因为,最近一段时间看到有人在问std::optional有什么用,以及不理解为什么要有这个类。所以打算简单介绍一下std::optional,重点讨论返回值相关的内容。

optional_2">1 std::optional

  • 类模板 std::optional 管理一个可选的包含值,即可能存在也可能不存在的值。
#include <iostream>
#include <optional>
int main()
{std::optional<std::string> a{"abc"};std::optional<std::string> b;if(a) {std::cout << "a = " << *a << "\n";}if(!b) {std::cout << "b is not set\n";}return 0;
}
  • optional 的一个常见用例是可能失败的函数的返回值。
#include <iostream>
#include <optional>std::optional<int> getScore(int id) {// dosomethingbool searchSuccessed = doSearchScoreExist(id);if(searchSuccessed) {int score = doSearchScore(id);return score;}return std::nullopt;
}int main()
{int validId = 2323131;int invalidId = 231310998;if(getScore(valid_id)) {std::cout << "search OK! id = " << validId << "\n";} else {std::cout << "search fail! id = " << validId << "\n";}if(getScore(invalidId)) {std::cout << "search OK! id = " << invalidId << "\n";} else {std::cout << "search fail! id = " << invalidId << "\n";}return 0;
}
  • 它包含有两个优点,一是更好的可读性;二是可更好处理高成本对象(来自于cppreference描述,但存疑)。

对于可读性来说,在c++中为了实现可能存在或者可能不存在的值的功能时。我们可能会选择 创建一个结构体来描述它(坏处是每个类型都需要创建一个这样的结构体)、使用std::pair(坏处是不容易看懂什么意思)、自己造轮子(耗时,还得自己写测试)。
对于高成本对象来说,我未做深入测试,但是从布局来看,它和std::pair<T,bool>相比并没有什么空间和时间的优势。std::optional的可能构成方式:

struct EmptyByte {};
template<class T>
class optional
{bool engaged;union {T value;EmptyByte emptyByte;};
};

2 函数返回值

这里为了方便,我们扩展前面的成绩查询示例:

class ScoreManager {
public:void addScore(int id,int score);int getScore(int id);
};

上述的接口有几个问题,我们分别来看:

2.1 缺少返回值

无论是add还是get,均至少需要对用户返回一个结果,代表着运行正确或者错误。
对于get来说,有的人可能会考虑到score最小值是0,那么我用-1来代表它的结果是有效的。首先,这种使用“魔法值”来判定程序状态的返回值,存在几个重大问题:
      一是对用户来说,极其容易用错,因为他需要阅读你的接口实现或者相关的注释才会知道这件事,更何况大部分情况下都是不读/不写注释的;
      第二是代码出现bug,极其难以定位,假设某个平均成绩计算错误,在复杂场景中,根本无法确定问题;
      第三是影响用户的单元测试,假设其他人调用了getScore,却不知道getScore还有失败的时候,它的单元测试根本覆盖不到这里。

接下来,我们做一定的补全:

class ScoreManager {
public:bool addScore(int id,int score);std::optional<int> getScore(int id);
};

2.2 没有对返回值的处理做限制

因为我们的addScore和getScore都可能会出现失败的情况,用户有可能会不对这种失败的情况做验证,因此,我们需要用户必须去处理返回值。我们期望的是这样:

void good()
{auto ret = scoreManager.getScore(0);if (!ret) {// dosomething}if(!scoreManager.addScore(1,0)) {// dosomething}
}// 我们期望没有处理返回值时,报错或者警告
void bad()
{scoreManager.addScore(10,10);scoreManager.getScore(5);
}int main()
{ScoreManager scoreManager;good();bad();return 0;
}

在这种情况下,我们可以使用c++中的[[nodiscard]]属性,该属性的作用是:
如果将声明了nodiscard的函数的返回值忽略,则编译器发出警告。

我们更新一下ScoreManager:

class ScoreManager {
public:[[nodiscard]]bool addScore(int id,int score);[[nodiscard]]std::optional<int> getScore(int id);
};

2.3 没有失败时的报错信息

对于getScore接口来说,我们期望在它错误时能反馈一些信息给用户,以提示它的错误原因。这时不得不夸一下Rust的Result了,来看看它是如何做的:

fn div(x: f64, y: f64) -> Result<f64, MathError> {if y == 0.0 {// 此操作将会失败,那么(与其让程序崩溃)不如把失败的原因包装在// `Err` 中并返回Err(MathError::DivisionByZero)} else {// 此操作是有效的,返回包装在 `Ok` 中的结果Ok(x / y)}
}

上述的接口对用户来说,如果运行正确,则返回结果;否则,返回错误的原因。在c++当中没有自带这样的接口,不过,我们可以自己简单的实现一下:

template<typename V,typename E>
class Reust
{
private:std::optional<V> value;std::optional<E> error;Result(const std::optional<V> &value,const std::optional<E> &error):value(value),error(error){}
public:static Reust<V,E> Ok(V &&v) {return Result(std::forward<V>(v),std::nullopt);}static Reust<V,E> Err(E &&e) {return Result(std::nullopt,std::forward<E>(e));}bool isError() {return error.has_value();}const V & getValue() {return value.value();}const V & getError() {return error.value();}
};

到这里,我们造好了这个玩具轮子,接下来更新ScoreManager:

class ScoreManager {
public:enum class ManagerError{...};[[nodiscard]]std::optional<ManagerError> addScore(int id,int score);[[nodiscard]]Reust<int,std::ManagerError> getScore(int id);
};

因为对于addScore来说的话,它仅需要错误时的原因,所以采取了std::optional;对于getScore来说,我们支持了返回值和错误信息两点。


http://www.ppmy.cn/news/1543724.html

相关文章

模拟算法 (算法详解+例题)

目录 一、什么是模拟二、模拟算法的特点和技巧三、模拟OJ题3.1、替换所有的问号3.2、提莫攻击3.3、N字形变换3.4、外观数列3.5、数青蛙 一、什么是模拟 模拟是对真实事物或者过程的虚拟。在编程时为了实现某个功能&#xff0c;可以用语言来模拟那个功能&#xff0c;模拟成功也…

分享几款开源好用的图片在线编辑,适合做快速应用嵌入

图片生成器是指一种工具或软件&#xff0c;用于自动生成图片或图像内容&#xff0c;通常依据用户设定的参数或模板进行操作。这种工具能够帮助用户快速创建视觉效果丰富的图像&#xff0c;而无需具备专业的设计技能。 在数字化时代&#xff0c;图片编辑已经成为日常工作和生活的…

【ChatGP】让ChatGPT解释和简化复杂的技术概念

让ChatGPT解释和简化复杂的技术概念 在科技迅速发展的今天&#xff0c;许多人面临着理解复杂技术概念的挑战。无论是初学者还是专业人士&#xff0c;能够轻松理解并运用这些概念都是至关重要的。ChatGPT作为一个强大的语言模型&#xff0c;可以帮助用户解释和简化复杂的技术概…

【深度学习基础】深入理解 卷积与卷积核

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a; &#x1f3c0;深度学习_十二月的猫的博客-CSDN博客 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 目录 1. 卷积 1.1 …

Python 自动化运维:安全与合规最佳实践

Python 自动化运维&#xff1a;安全与合规最佳实践 目录 &#x1f512; Python安全编程实践与最佳实践&#x1f511; 使用Hashlib与Cryptography进行数据加密&#x1f4ca; 安全审计与合规检查的重要性&#x1f50d; 处理敏感数据与隐私保护的方法 1. &#x1f512; Python安…

python/Django创建应用(app)

注意事项&#xff1a; Django已经安装在你的Python环境中。 你已经创建了一个Django项目&#xff0c;并且当前工作目录是项目的根目录。你的虚拟环境&#xff08;如果使用&#xff09;已经被激活。 在原有Django项目的控制台 python manage.py startapp myapp 创建一个应用&…

Spring Boot的核心优势及其应用详解

目录 前言1. Spring Boot的核心优势1.1 启动依赖的集成1.2 自动化配置 2. 内嵌服务器支持2.1 内嵌Tomcat服务器2.2 独立运行与便捷部署 3. 外部配置管理3.1 多环境支持3.2 配置优先级与外部化配置 4. Spring Boot的应用场景4.1 微服务架构4.2 云原生应用 结语 前言 在现代的Ja…

k8s Sidecar代理

Sidecar 代理是一种常见的设计模式&#xff0c;在 Kubernetes 和微服务架构中经常被用来增强服务的功能和隔离服务的职责。Sidecar 代理作为一个附属的进程或容器&#xff0c;通常与主应用容器一起部署在同一个 Pod 中&#xff0c;负责处理一些非业务的通用任务&#xff0c;比如…