第 18 節

现代C++

0瀏覽次數0訪問次數--跳出率--平均停留

"现代 C++"通常指 C++11 以及之后的标准引入的一系列写法和库工具。它不是把旧 C++ 全部推翻,而是在原有语法之上,提供更安全、更清晰、更适合工程开发的表达方式。

本章按"由浅入深、从常用到进阶"的顺序组织:先学每天都会用到的语法,再学生命周期和资源管理,接着学回调、数据表达、时间、文件和并发,最后了解模块化编译。

本章例程约定

为了方便你直接复制验证,本章各小节的"示例代码"遵循这些约定:

  1. 示例代码都包含完整 #include
  2. 示例代码都包含 main 函数。
  3. 示例代码可以复制到单个 .cpp 文件中编译运行。
  4. 每个示例代码下方都给出运行结果。
  5. 示例尽量一次只增加一个新概念,避免一个例子塞太多东西。

文中的"常见错误"用于说明不要这样写。为了避免误复制,错误写法会尽量放在文字说明或短片段中,不当作可运行示例。

学习顺序

阶段先解决的问题对应章节
基础表达少写重复类型,减少空指针和枚举混用,遍历更清楚autonullptrusingenum class、范围 for、结构化绑定
生命周期资源什么时候释放,谁拥有对象,大对象怎么高效传递constexpr、RAII、智能指针、右值引用和移动语义
回调和数据模型小函数、回调保存、参数适配、可能没有值、多种类型之一Lambda、std::functionstd::bindstd::optionalstd::variantstd::span
系统能力格式化输出、时间、并发、文件系统、模块化std::format / std::printstd::chrono、并发编程、std::filesystem、modules

现代 C++ 的重点不是"语法更新",而是"把意图写清楚"。例如:

  • nullptr 让空指针和整数 0 分开。
  • enum class 让不同枚举类型不能随便混用。
  • RAII 和智能指针把资源释放交给对象生命周期。
  • Lambda 让局部回调写在使用现场。
  • std::function 把不同类型的可调用对象统一保存和传递。
  • std::format / std::print 让格式化输出更简洁,并保持类型安全。

旧写法和现代写法的关系

很多现代 C++ 特性都来自一个朴素问题:旧写法能用,但在复杂场景里容易出错。

旧写法主要问题现代写法
NULL / 0 表示空指针会和整数重载混淆nullptr
typedef 写类型别名模板别名不直观using
普通枚举枚举名污染作用域,可隐式转整数enum class
手动 new/delete提前返回、异常、重复释放都容易出错RAII、std::unique_ptrstd::shared_ptr
手动 lock/unlock忘记解锁会死锁,异常路径更危险std::lock_guardstd::unique_lock
远处定义普通函数做简单回调局部逻辑被拆散,不能方便携带上下文Lambda
只用函数指针保存回调不能保存有捕获的 lambda 和函数对象std::function
手写适配函数调整参数代码重复,旧接口适配麻烦std::bind 或 Lambda
用特殊值表示失败-1、空字符串等魔法值语义不清std::optional
union 表示多种类型访问错误类型会产生未定义行为std::variant
手写时间单位换算秒、毫秒、微秒容易混std::chrono
平台相关文件 APIWindows/Linux 写法不同std::filesystem

学习每个特性时,可以按三个问题来理解:

  1. 旧写法哪里容易错?
  2. 现代写法如何把意图写进类型或语法里?
  3. 在什么场景下差异才明显?

比如一个智能指针示例如果只在 main 里创建对象然后正常结束,看起来和手动 delete 差不多;一旦出现提前 return、异常、跨函数传递,RAII 的价值就会立刻显现。一个 Lambda 如果只立刻调用,按值捕获和按引用捕获都可能没事;一旦保存到回调、线程、定时器里,生命周期差异就会变得非常重要。

推荐学习路线

让代码更清楚

先学习 autonullptrusingenum class、范围 for 和结构化绑定。这些内容难度不高,但能明显减少冗长代码和低级错误。

这一阶段重点记住:

  • auto 不是弱类型,只是让编译器帮你写类型。
  • nullptr 只表示空指针,不表示整数。
  • enum class 默认不和整数混用。
  • 范围 for 默认用 const auto& 遍历大对象。
  • 结构化绑定适合解包 pairtuple、结构体和 map 元素。

理解生命周期

接着学习 constexpr、RAII、智能指针和移动语义。这一阶段比语法更重要的是思维方式:对象什么时候创建,什么时候销毁,谁拥有它,谁只是借用它。

建议按这个顺序理解:

  1. RAII:资源和对象生命周期绑定。
  2. unique_ptr:独占所有权,默认选择。
  3. shared_ptr:确实需要多个所有者时才用。
  4. weak_ptr:只观察,不延长生命周期,用来打破循环引用。
  5. 移动语义:把资源转移出去,避免大对象深拷贝。

掌握回调和数据表达

Lambda、std::functionstd::bind 三章要连起来看,但不要混在一起背:

工具解决的问题重点
Lambda在使用现场写一个小函数捕获列表、参数、返回值、生命周期
std::function统一保存和传递不同类型的可调用对象类型擦除、回调成员变量、回调容器
std::bind适配已有函数的参数固定参数、调整顺序、绑定成员函数

std::optionalstd::variantstd::span 则分别解决"可能没有值"、"几种类型之一"、"借用一段连续数据"的问题。

走向工程能力

最后学习格式化输出、时间、并发、文件系统和 modules。这些内容更接近真实项目:

  • std::format / std::printstringstream 简洁,比 printf 类型安全。
  • std::chrono 避免手写时间单位换算。
  • 并发编程要先保证正确,再考虑速度。
  • std::filesystem 让路径、目录、文件信息处理跨平台。
  • modules 是 C++20 引入的新编译模型,目前应先理解概念,再根据工具链支持决定是否使用。

实战练习建议

  1. nullptr:写两个重载函数,分别接收 intint*,观察 0NULLnullptr 的区别。
  2. enum class:把普通枚举换成枚举类,看看哪些隐式转换被禁止。
  3. 学智能指针:先写一个提前 return 的手动 new/delete 例子,再改成 unique_ptr
  4. 学 Lambda:分别用按值捕获和按引用捕获,观察外部变量是否变化。
  5. std::function:把多个不同 lambda 放进同一个 vector,统一执行。
  6. 学并发:让两个 1 秒等待任务先顺序执行,再放进两个线程中执行,对比耗时。
音乐页