CS106L 14

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"")更直观。
  1. 避免未初始化值
    • std::optional 默认初始化为空 (std::nullopt),不会像 T 那样可能未定义行为。
  2. 更好的错误处理
    • 结合 has_value()if (opt) 显式检查是否有值,避免不必要的异常或默认值分支。

缺点 (Cons)

  1. .value() 可能导致 bad_optional_access
    • opt.value()opt 为空时抛出异常,必须显式检查 has_value(),或者用 value_or(default)
  2. std::optional<T&> 受限
    • 直接存 T& 可能导致悬垂引用(指向已销毁的对象),因此 std::optional<T&> 不常用 ,但可以用 std::reference_wrapper<T> 作为替代。
  3. 性能开销
    • std::optional<T> 比直接返回 T 有额外的存储和检查开销 ,尤其是存储大对象时,相比返回 T*T& 可能有性能损失。

如何避免 bad_optional_access

  1. 显式检查

    cpp 复制代码
    std::optional<int> opt;
    if (opt) {
        std::cout << *opt << std::endl;
    }
  2. 使用 value_or()

    cpp 复制代码
    int x = opt.value_or(42); // 如果 opt 为空,返回 42
  3. 返回 std::optional<std::reference_wrapper<T>>

    cpp 复制代码
    std::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...

相关推荐
zhangzhangkeji3 小时前
c++ 定点 new 及其汇编解释
开发语言·c++
Pakho love3 小时前
Linux:文件与fd(被打开的文件)
android·linux·c语言·c++
W说编程4 小时前
C语言指针专题四 -- 多级指针
c语言·开发语言·数据结构·c++·嵌入式硬件
我命由我123454 小时前
游戏引擎 Unity - Unity 下载与安装
c语言·开发语言·c++·后端·unity·c#·游戏引擎
h^hh5 小时前
向下调整算法(详解)c++
数据结构·c++·算法
我命由我123455 小时前
游戏引擎 Unity - Unity 启动(下载 Unity Editor、生成 Unity Personal Edition 许可证)
c语言·c++·后端·unity·c#·游戏引擎·ue4
王老师青少年编程6 小时前
gesp(C++六级)(4)洛谷:B3874:[GESP202309 六级] 小杨的握手问题
开发语言·c++·算法·gesp·csp·信奥赛
h^hh6 小时前
priority_queue的创建_结构体类型(重载小于运算符)c++
开发语言·数据结构·c++·算法
你好 Future!7 小时前
文件读写操作
c++
菜一头包8 小时前
线程池以及在QT中的接口使用
c++·qt