CS106L 14
Recap
- Move semantics
- 迁移数据
- 对左右值都可以使用
- 如果没有使用,则不需定义
Type Safety
之前的课程中,如下操作是错误的,如何保证编译真确且出现错误提示?
cpp
void f(int x) {
return x / 3;
}
f("String") // Compile Error
如下函数:
-
vec.back()
返回最后一个元素,不删除 -
vec.pop_back()
删除最后一个元素,但是不返回
cpp
void removeOddsFromEnd(vector<int>& vec) {
while(vec.back() % 2 == 1) {
vec.pop_back();
}
}
Problem:
- 如果vector为空?执行还正确吗?
- 如果为空,则
vec.back()
返回的是不确定的行为:可能是垃圾,可能是一些值,可能会引发崩溃
cpp
void removeOddsFromEnd(vector<int>& vec) {
while(!vec.empty() && vec.back() % 2 == 1) {
vec.pop_back();
}
}
Problem:
- vector可能传入时为空,可能不为空,如何明确的区分这两种行为?
back的默认实现
cpp
valueType& vector<valueType>::back() {
return *(begin() + size() - 1);
}
当vector中没有元素时,抛出警告
cpp
valueType& vector<valueType>::back() {
if(empty()) throw::out_of_range;
return *(begin() + size() - 1);
}
当不存在该类型的值时,该怎么办?back承诺返回该类型的值
cpp
std::pair<bool, valueType&> vector<valueType>::back() {
if(empty()) return {false, valueType()}
return {true, *(begin() + size() - 1)};
}
Problem:
- back() 仍然没有解决vector 是传入时是已经为空
- valueType() 可能没有默认构造
- 如果默认构造函数的值随机,则符合程序要求吗?
:thinking:我们应该怎么办?
Std::optional
是一个模板类,它要么包含类型 T 的值,要么不包含任何内容(表示为 nullopt)
nullopt (可以转换为任意可选类型值的对象)!= nullptr(空指针)
.value
方法,返回所包含的值或者抛出bad_optional_access
错误.value_or(valueType val)
返回包含的值或者默认值.has_value
返回包含的值存在返回true,否则返回假
cpp
void main() {
std::optional<int> num1 = {};
num1 = 1;
num1 = std::nullopt;
}
最终num1
的值为空,但类型仍是std::optional<int>
cpp
std::optional<valueType> vector<valueType>::back() {
if(empty()){
return {};
}
return *(begin() + size() - 1);
}
为解决问题:
cpp
void removeOddsFromEnd(vector<int>& vec) {
while(vec.back().has_value() && vec.back().value() % 2 == 1) {
vec.pop_back();
}
}
cpp
void removeOddsFromEnd(vector<int>& vec) {
while(vec.back().value_or(2) % 2 == 1) {
vec.pop_back();
}
}
如下次代码是错误的:
cpp
int thisFunctionSucks(vector<int>& vec){
return vec[0];
}
Problem:
- 如果vec是空怎么办?
错误,引用必须始终绑定到有效对象,而可选的不能保证
cpp
std::optional<valueType&> 错误!!!!
vector<valueType>::operator[](size_t index){
return *(begin() + index);
}
通过.at
方法
cpp
valueType& vector<valueType>::operator[](size_t index){
return *(begin() + index);
}
valueType& vector<valueType>::at(size_t index){
if(index >= size()) throw std::out_of_range;
return *(begin() + index);
}
优点 (Pros)
- 更清晰的 API 设计
- 返回
std::optional<T>
明确表示"可能没有值",比返回nullptr
或特殊值(如-1
或""
)更直观。
- 返回
- 避免未初始化值
std::optional
默认初始化为空 (std::nullopt
),不会像T
那样可能未定义行为。
- 更好的错误处理
- 结合
has_value()
或if (opt)
显式检查是否有值,避免不必要的异常或默认值分支。
- 结合
缺点 (Cons)
.value()
可能导致bad_optional_access
opt.value()
在opt
为空时抛出异常,必须显式检查has_value()
,或者用value_or(default)
。
std::optional<T&>
受限- 直接存
T&
可能导致悬垂引用(指向已销毁的对象),因此std::optional<T&>
不常用 ,但可以用std::reference_wrapper<T>
作为替代。
- 直接存
- 性能开销
std::optional<T>
比直接返回T
有额外的存储和检查开销 ,尤其是存储大对象时,相比返回T*
或T&
可能有性能损失。
如何避免 bad_optional_access
-
显式检查
cppstd::optional<int> opt; if (opt) { std::cout << *opt << std::endl; }
-
使用
value_or()
cppint x = opt.value_or(42); // 如果 opt 为空,返回 42
-
返回
std::optional<std::reference_wrapper<T>>
cppstd::optional<std::reference_wrapper<int>> get_ref(std::vector<int>& v, size_t i) { if (i < v.size()) return v[i]; return std::nullopt; }
std::optional
提高了代码的安全性和可读性,但要谨慎使用 .value()
,在某些情况下,std::optional<T&>
不是最好的选择,可用 std::reference_wrapper<T>
代替。
Other
关于std::optional
的其他使用方法:
.and_then(function f)
如果包含的值存在则调用 f(value)的结果,否则返回nullopt
其中f
必须返回optional
transform(function f)
如果包含的值存在则调用 f(value)的结果,否则返回nullopt
其中f
必须返回optional<value Type>
or_else(function f)
如果存在则返回值,否则返回调用 f 的结果
cpp
void removeOddsFromEnd(vector<int>& vec){
auto isOdd = [](optional<int> num){
if(num)
return num % 2 == 1;
else
return std::nullopt;
//return num ? (num % 2 == 1) : {};
};
while(vec.back().and_then(isOdd)){
vec.pop_back();
}
}
其他语言的设计模式
- Rust 保证内存和线程安全的系统语言
- Swift Apple 的语言,专为应用程序开发而设计
- JaveScript 每个人都可以使用
Recap std::optional
可以通过使用严格的类型系统来保证程序的行为!
std::optional
是一个可以实现这一点的工具:它允许返回一个值或空值,可使用.has_value()
、.value_or()
、.value()
进行操作。- 但它可能较为笨重且性能较低,因此 C++ 大多数 STL 数据结构 并不使用
std::optional
。 - 然而,许多其他编程语言都会使用它!
- 除了在类中使用
std::optional
,还可以在应用代码中合理地使用它,这也是强烈推荐的做法!
本节课主要学习了std::optinal
的值可能为空,也可能没有值的概念,适用于返回值可能为空的情况。
End...