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...

相关推荐
AC赳赳老秦3 小时前
采集行为合规自检:OpenClaw 自动校验 robots 协议与采集频率,规避违规采集风险
java·开发语言·c++·python·php·deepseek·openclaw
无名猿4 小时前
C++ 查找算法:std::find 与容器成员 find 的性能差距
c++·性能优化·stl·标准库
程序猿编码4 小时前
工业视觉多任务方案:可编程梯度信息视觉框架,训练评估 + C++ 部署完整实战
开发语言·c++·视觉·推理引擎
朝朝辞暮i5 小时前
C++ 第 21 课:struct —— 把一组相关数据打包在一起
开发语言·c++·算法
SHARK_pssm6 小时前
【C++——类和对象(下)】
开发语言·c++·经验分享·笔记
汉克老师7 小时前
GESP2026年9月认证C++二级( 第一部分选择题(1~7题)精讲
c++·gesp·小学生·学c++编程
沫璃染墨7 小时前
《从零入门Linux系统篇(五十四):线程篇·七——互斥锁底层原理:从原子交换到线程竞争与锁实现》
linux·运维·服务器·开发语言·c++·驱动开发·系统架构
可乐ea9 小时前
GitHub Security Lab 开源 Fuzzing Taskflow:让 LLM 智能体自己跑完 C/C++ 模糊测试
jvm·c++·github·模糊测试·mcp·代码agent·智能体自动化
朝朝辞暮i9 小时前
C++ 第 13 课:值传递 —— 为什么函数里改了,外面却没变?
java·c++·算法
无忧.芙桃10 小时前
C++语言原理与实践(十):vector类的底层实现
开发语言·c++·算法