14 类型安全、std::optional⚓︎
约 911 个字 66 行代码 预计阅读时间 5 分钟
类型安全:编程语言预防类型错误的效果程度,保证程序的行为正常。
观察下面的代码:
它的问题是:当 vec
是一个空向量时,调用该函数就会产生未定义的行为(undefined behavior):要么函数运行会失败,要么会得到垃圾值,要么得到一些意外的真实存在于内存的值。
解决方法:
- 修改
while
语句为:while(!vec.empty() && vec.back() % 2 == 1)
,也就是说我们程序员应该预先设置vec
为非空的条件,避免未定义的行为发生。 - 修改
vec.back()
函数,使其能够应对各种情况,从而保证确定性的行为
对于后一种方法,假设我们原来的 vec.back()
函数的定义如下:
如果 vector
为空,那么返回语句的指针将指向未知的一个内存未知,此时对该指针解引用就会造成未定义的行为。其中一种修改方案是在返回语句前先判断 vector
是否为空(本质上同方法一if (empty()) throw std::out_of_range;
。
还可以有一个小改进:让函数再返回一个调用是否成功的信息,修改结果如下:
std::pair<bool, valueType&> vector<valueType>::back()
{
if (empty())
// 默认 valueType 存在构造函数,但调用成本较高
return {false, valueType()};
return {true, *(begin() + size() - 1)};
}
std::optional⚓︎
std::optional
是一种类模板,它的值要么是指定类型 T
,要么为空(即 nullopt
)
注:
nullopt
\(\ne\)nullptr
(虽然我不清楚)nullptr
是什么
下面这两条语句的意义相同:
利用 std::optional
,之前的 vec.back()
函数可以修改为:
std::optional<valueType> vector<valueType>::back()
{
if (empty())
return {};
return *(begin() + size() - 1);
}
但这样修改后又出现一个问题:因为 std::optional
类型不能进行算术运算,所以与之前的 removeOddsFromEnd()
函数发生冲突。但是我们可以使用 value()
方法获取它的值,使其可以进行算术运算:
void removeOddsFromEnd(vector<int>& vec)
{
while(vec.back().has_value() && vec.back().value() % 2 == 1)
{
vec.pop_back();
}
}
也许你认为
while
语句的判断条件有点太长了,想要偷懒一点,将vec.back().has_value
改为vec.back()
。虽然这样可行,但是请不要这么做! (具体原因目前不是很清楚)
关于 std::optional
的一些接口或方法
.value()
:返回std::optional
变量的值,或者抛出bad_optional_access
错误.value_or(valueType val)
:返回变量的值,或者设置好的默认值val
.has_value()
:当变量存在值的时候返回true
,否则返回false
.and_then(function f)
:如果值value
存在,返回调用f(value)
的结果,否则为nullopt
(f
的返回类型必须是optional
).transform(function f)
:如果值value
存在,返回调用f(value)
的结果,否则为nullopt
(f
的返回类型必须是optional<valueType>
)or_else(function f)
:若存在值value
则返回其本身,否则返回调用f
的结果
remobeOddsFromEnd()
函数最终版本
例子
下面是一段更烂的代码:
同样的问题:如果 vec
没有任何元素,访问 vec
就是一个未定义的行为。解决方法是不要用方括号访问元素,而是用 .at()
方法访问,相比前一种方法更加安全,这可以从它们的实现代码中看出:
使用 std::optional
作为函数返回类型的优劣:
- 优:
- 能够使函数返回更有意义的内容
- 类函数调用行为的正确性、安全性得到保证
- 劣:
- 需要到处使用
.value()
方法 - (在 C++ 中)很可能会遇到
bad_optional_access
错误 - (在 C++ 中)该类型自身也存在未定义的行为(比如在使用
.value()
时没有进行错误检查) - ...
- 需要到处使用
由于 std::optional
比较笨重,且运行速度慢,所以 C++ STL 的数据结构一般不会用到它。但是很多别的编程语言会用到类似 std::optional
的东西,比如 Rust、Swift、JavaScript 等。
评论区