一、前言
我们在写代码过程中可能经常会碰到这样的情况:
某个函数经过一系列计算后获取一个返回值,但是这个函数可能在执行过程中有异常分支,从这些异常分支中return出来的时候还没有得到这个想计算的值。 例如想计算得到一个无符号类型,我们可能会在这些异常分支return一个-1出去,用来表示计算失败,或执行有误,甚至不同的负值用来表示不同的异常情况。。。由于这些值的存在,本来返回值应该是uint32类型,为了容纳可能出现的负值和整个uint32的范围,返回值就变成了int64。。。这个int64,竟然要么表示错误码,要么表示计算结果?这给阅读和使用上都造成了不便。此外,这种负值的出现还引入了魔鬼数字。
举个栗子:(伪代码,并不能跑,只是用作举例)
#include
int64_t getuintvalue() {
if (xxx) {
return -1; // malloc fail
}
if (xxx) {
return -2; // invalid param
}
// do some work
return somevalue;
}
或者是一个计算布尔值的函数,失败时也返回false吗?调用者拿到这个值一脸懵逼。。
bool getboolvalue() {
if (xxx) {
return false; // 计算失败时返回的false
}
// do some work
return false; // 计算成功时返回的false
}
还有一种情况发生在枚举中。通常为了应对枚举类型暂时还不知道时,我们会新增一个枚举值叫unknown。这样以来,任何一个使用该枚举类型的地方都要首先判断是不是unknown,造成代码冗余。举个栗子:
enum color {
red,
blue,
green,
unknown
};
void printcolor(color c) {
// 这里还要不要判断是不是unknown?
// 如果不判断,其它地方复用这个函数时怎么办?
}
void judgecolor(color c) {
if (c != unknown) {
printcolor(c);
}
}
c 17新增了std::optional来解决这个问题。
(gcc7/clang4/msvc19.10以上才支持optional,首先先检查你的编译器版本)
optional是一个模板类:
template
class optional;
它内部有两种状态,要么有值(t类型),要么没有值(std::nullopt)。有点像t*指针,要么指向一个t类型,要么是空指针(nullptr)。
std::optional有以下几种构造方式:
#include
#include
using namespace std;
int main() {
optional o1; //什么都不写时默认初始化为nullopt
optional o2 = nullopt; //初始化为无值
optional o3 = 10; //用一个t类型的值来初始化
optional o4 = o3; //用另一个optional来初始化
return 0;
}
查看一个optional对象是否有值,可以直接用if,或者用has_value()
#include
#include
using namespace std;
int main() {
optional o1;
if (o1) {
printf("o1 has value\n");
}
if (o1.has_value()) {
printf("o1 has value\n");
}
return 0;
}
当一个optional有值时,可以通过用指针的方式(*号和->号)来使用它,或者用.value()拿到它的值:
#include
#include
#include
using namespace std;
int main() {
optional o1 = 5;
printf("%d\n", *o1);
optional o2 = "hello";
printf("%s\n", o2->c_str());
printf("%s\n", o2.value().c_str());
return 0;
}
将一个有值的optional变为无值,用.reset()。该函数会将已存储的t类型对象析构掉
#include
#include
using namespace std;
int main() {
optional o1 = 5;
o1.reset();
}
于是在第一章中的3个问题可以这样改写:
#include
#include
using namespace std;
optional getuintvalue() {
if (xxx) {
return nullopt; // malloc fail
}
if (xxx) {
return nullopt; // invalid param
}
// do some work
return somevalue;
}
#include
using namespace std;
optional getboolvalue() {
if (xxx) {
return nullopt; // 计算失败时返回
}
// do some work
return false; // 计算成功时返回的false
}
#include
using namespace std;
enum color {
red,
blue,
green
};
void printcolor(color c) {
// 这里永远都不需要判断这个值是否合法
}
void judgecolor(optional c) {
if (c.has_value()) {
printcolor(c.value());
}
}
可读性暴增!
撸了一个极其简易的optional
#include
#include
#include
#include
using namespace std;
class nullopttype {
};
nullopttype nullopt;
template> * = nullptr>
class optional {
public:
optional() = default;
virtual ~optional() = default;
optional(nullopttype) {}
optional(const t &v) {
m_data = make_unique(v);
m_hasvalue = true;
}
optional(const optional &v) {
if (v.hasvalue()) {
m_data = make_unique(value());
m_hasvalue = true;
}
}
bool hasvalue() const {
return m_hasvalue;
}
explicit operator bool() const {
return m_hasvalue;
}
const t &value() const {
if (m_hasvalue) {
return *m_data;
} else {
throw std::exception();
}
}
const t &operator*() const {
return value();
}
void reset() {
m_data.reset();
m_hasvalue = false;
}
private:
bool m_hasvalue = false;
unique_ptr m_data = nullptr;
};
int main() {
optional o1;
if (o1) {
printf("o1 has value\n");
}
optional o2 = nullopt;
if (o2) {
printf("o2 has value\n");
}
optional o3 = 10;
if (o3) {
printf("o3 has value %d\n", o3.value());
}
optional o4 = string("haha");
if (o4.hasvalue()) {
printf("o4 has value %s\n", (*o4).c_str());
}
//optional o5; 编译失败
}