Skip to content
feixiaoPublic

About

现代C++

Resources

Stars

0 stars

Watchers

1 watching

Forks

Latest commit

 

History

19 Commits

Folders and files

Repository files navigation

现代 C++ 快速入门

本仓库收集了 275 个现代 C++ 示例文件,内容覆盖 C++11、C++14、C++17 和 C++20。示例按章节放在 src 目录中,适合围绕单个语言特性阅读、修改和验证。

参考资料:《Modern C++ Tutorial: C++11/14/17/20》

学习每个特性时,不只看“语法怎么写”,还要回答三个问题:它解决什么问题、什么时候适合用、相较原有写法有什么具体好处。下面的章节索引按这三个维度组织,并保留源码入口。

目录结构

.
├── CMakeLists.txt       # 7 个早期示例的构建入口
├── ReadMe.md            # 项目说明和源码导航
└── src/
    ├── 1-1.cpp
    ├── 2-1.cpp
    ├── 2-2.cpp
    └── ...              # 共 42 章、275 个示例

src/<章节>-<序号>.cpp 是统一命名规则。例如,6-1.cpp 到 6-9.cpp 都属于第 6 章,主题是移动语义和完美转发。同一章通常通过一组相近代码展示正确写法、错误写法及版本差异。

5 分钟熟悉 src

  1. 从下方章节索引定位感兴趣的语言特性。
  2. 先读该章的语法作用和使用场景,再打开源码入口,识别 main()、核心类型和关键表达式。
  3. 按编号比较同章示例:原有写法有什么成本,新特性减少了哪些重复、错误或资源开销?
  4. 单独编译并运行一个示例,不要直接假设整章都能批量构建。
  5. 修改输入、类型或限定符,再观察编译结果和输出。

以下命令适合快速查看和比较一章代码:

# 列出第 6 章全部示例
printf '%s\n' src/6-*.cpp

# 查找第 41 章中的 Concepts 和约束表达式
rg -n 'concept|requires' src/41-*.cpp

# 比较两个相邻示例
diff -u src/21-1.cpp src/21-2.cpp

章节索引

这里的“传统写法”指引入对应特性前的常见实现:多数以 C++98/03 为参照,C++17/20 特性也会与 C++11/14 写法比较。numeric_limits、链式调用等本来就能用于传统 C++,不应当作新标准独有的能力。收益也不等于运行得更快;很多特性的价值在于类型安全、作用域控制和维护成本。

章节与源码入口 语法作用 典型使用场景 传统写法 → 具体好处
1 · 整数边界 用 numeric_limits<T> 查询类型取值范围等属性 泛型数值算法、计算前检查边界 硬编码边界或按类型选宏 → 统一按类型查询,减少平台假设;它本身不阻止溢出
2 · 命名空间 组织名称;内联命名空间可暴露默认版本,嵌套语法简化声明 库的 API 版本管理、分层组织代码 名称前缀、手工转发和层层嵌套声明 → 隔离冲突,简化默认版本访问;普通命名空间早已存在
3 · auto 非类型模板参数 C++17 起由传入的编译期值推导模板参数类型 编译期常量包装、以值配置模板 template<class T, T N> → template<auto N>,调用者不必重复提供值的类型
4 · decltype(auto) C++14 起按 decltype 规则推导类型,可保留引用 转发函数返回值、包装访问器 手写返回类型或复杂类型萃取 → 随表达式保留类型;括号会影响推导,仍须防止悬垂引用
5 · 返回类型推导 用尾置返回类型表达参数相关类型;C++14 支持 auto 返回推导 泛型运算、返回类型很长的辅助函数 手写返回类型、辅助 traits → 减少重复声明,让接口跟随表达式类型
6 · 移动与完美转发 T&&、std::move、std::forward 支持资源转移及值类别传递 大缓冲区、独占资源、泛型工厂 深拷贝、手工 swap、成组重载 → 类型支持移动时可避免复制资源;转发可减少重载重复
7 · Lambda 就地定义可调用对象并捕获上下文 排序规则、算法谓词、局部回调 单独函数或具名函数对象 → 行为与使用位置放在一起,减少样板;引用捕获须关注生命周期
8 · 成员默认初始化 在非静态成员声明处给出默认初值 多个构造函数共享默认状态 每个构造函数重复初始化 → 集中维护默认值,减少漏初始化;构造函数仍可覆盖它
9 · 列表初始化 用花括号初始化对象,支持 initializer_list 并限制窄化转换 容器初值、对象构造、数值转换检查 多种初始化形式、隐式截断 → 写法更一致且可在编译期拒绝窄化;列表构造函数优先级可能改变含义
10 · default / delete 显式请求默认特殊成员函数,或禁止某个函数参与有效调用 禁止复制的资源类型、限制不希望的重载 私有且不定义的拷贝函数、手写空函数 → 意图更清楚,误用直接报错;合适的默认化声明可保留平凡性
11 · 联合体生命周期 允许联合体含非平凡成员,但需管理活动成员的构造与析构 带标签的紧凑存储、底层内存布局 原始字节缓冲和强制转换 → 能声明真实成员类型;管理仍复杂,通常优先考虑 C++17 std::variant
12 · 委托构造 一个构造函数委托同类的另一个构造函数完成初始化 多种构造入口共享初始化逻辑 重复初始化列表或构造后调用 init() → 复用真正的构造过程,集中建立对象不变量
13 · 继承构造 用 using Base::Base 引入基类构造函数;using 也可引入名称 不改变构造接口的薄派生类 为基类每个构造函数写转发版本 → 减少重复;派生类新增成员仍需正确初始化
14 · 强类型枚举 enum class 限定枚举项作用域,禁止隐式转整数,可指定底层类型 状态、错误码、协议字段类型 普通枚举泄漏名称并可混入整数运算 → 减少命名冲突和跨枚举误用,转换意图更明确
15 · 聚合初始化 按聚合规则直接初始化成员;C++20 支持指定成员初始化 简单配置、记录、数据载体 为纯数据类型编写样板构造函数或逐个赋值 → 直接表达初始数据;聚合资格随标准版本变化
16 · override / final 检查虚函数重写,或禁止继续重写、派生 多态接口、需要限制扩展的类型 靠人工核对签名和注释约定 → 编译器发现参数或限定符不匹配,强制执行扩展边界
17 · 范围 for 通过范围的迭代接口逐元素遍历 数组、容器及自定义范围 手写下标或迭代器循环 → 减少边界与递增错误;用 auto& / const auto& 避免不必要的元素复制
18 · 条件初始化语句 C++17 的 if / switch 可在条件前声明局部对象 查表后判断结果、带锁检查状态 提前声明临时变量或额外套作用域 → 将变量寿命限制在整个条件语句内,减少名称泄漏
19 · static_assert 在编译期检查常量条件并给出诊断 模板参数限制、大小与布局假设 运行期 assert 或负长度数组技巧 → 问题在编译时暴露,表达更直接且无运行期开销
20 · 元组与结构化绑定 tuple 组织多值;C++17 用 auto [a, b] 分解对象 多返回值、遍历键值对 输出参数、get<0> 或先声明再 tie → 为各部分直接命名并推导类型,减少位置索引与重复声明
21 · noexcept 声明不抛异常的契约,可按表达式计算条件 移动操作、swap、泛型异常保证 动态异常说明或仅写注释 → 可查询的编译期契约,使容器等能选择安全的移动路径;违约会终止程序
22 · 类型别名 用 using 定义别名及别名模板 简化复杂类型、复用容器与策略组合 typedef 和嵌套 type 的辅助模板 → 声明更易读,模板别名可直接使用;不会产生新类型
23 · nullptr 提供专门的空指针字面量及类型 指针初始化、重载调用、泛型传参 0 / NULL 兼有整数含义 → 避免误选整数重载,表达空指针意图更准确
24 · 三路比较 C++20 的 <=> 表达比较类别,可默认化成员比较 值类型、排序键、复合记录 手写多组关系运算符 → 减少重复和相互矛盾;默认成员顺序须符合业务语义,浮点数可能是偏序
25 · thread_local 为每个线程提供独立对象及线程存储期 线程私有缓存、统计、临时状态 平台 TLS API 或以线程 ID 管理映射 → 标准化声明与生命周期管理;不能据此保证所指向的共享数据安全
26 · 内联变量 C++17 允许满足规则的变量定义出现在多个翻译单元 仅头文件库的共享变量、静态数据成员 头文件 extern 加一个 .cpp 定义 → 可在头文件直接定义同一实体,简化链接组织;inline 不表示更快
27 · 常量求值 constexpr 支持常量求值,C++17 if constexpr 按条件丢弃分支 编译期表与参数、按类型选择模板实现 宏、枚举常量、模板特化或标签分派 → 用更普通的表达式和控制结构表达编译期逻辑;constexpr 函数也可在运行期执行
28 · 链式调用 返回当前对象的引用等结果以支持连续成员调用 字符串操作、配置接口 多条重复对象名的调用 → 紧凑表达连续操作;传统 C++ 已支持,收益来自接口设计而非新标准
29 · 字面量扩展 十六进制浮点表达二进制数值,分隔符改善读数,自定义后缀创建值 精确浮点常量、大数值、单位与领域类型 难读的数字和无单位裸值 → 更易核对数值;后缀配合专用类型可表达单位,单靠后缀不自动保证安全
30 · 内存对齐 alignas 指定对齐,alignof 查询要求,对齐分配支持过度对齐对象 SIMD 数据、硬件接口、自定义存储 编译器专用属性和手工地址调整 → 标准方式表达与检查对齐;性能收益仍需测量
31 · 标准属性 用 [[noreturn]]、[[deprecated]]、[[nodiscard]] 等表达意图 标记废弃接口、提醒处理结果、说明控制流 注释或厂商属性 → 可移植地向工具提供信息并获得相应诊断;并非所有属性都会强制报错
32 · 头文件检测 用 __has_include 在预处理时检测头文件是否可用 可选依赖、跨工具链构建 按编译器名称或版本猜测 → 直接探测头文件;是否支持某项 API 仍需特性宏或构建检查
33 · 协程 C++20 用 co_await / co_yield 等表达挂起和恢复 异步 I/O、惰性生成、异步流程组合 回调链或手写状态机 → 用接近顺序代码的形式表达可挂起流程;需要协程返回类型与调度支持,不自动创建线程
34 · 初始化与生命周期 用 explicit 控制隐式转换,并理解复制消除与对象构造规则 转换敏感的 API、工厂返回值、资源对象 依赖隐式转换或为避拷贝使用输出参数 → 接口意图更明确;C++17 指定纯右值场景保证直接构造,具名局部变量的 NRVO 不保证
35 · 可变参数模板 参数包支持任意数量的模板参数,C++17 折叠表达式组合运算 类型安全的日志、构造转发、多值运算 C 风格 ...、宏或固定数量重载 → 保留参数类型并减少重复;折叠表达式进一步减少递归模板
36 · 模板模板参数 将模板本身作为参数以注入类型构造方式 容器适配、可替换的模板策略 对每种容器重复实现或手工包装 → 算法与类型构造方式分离;该能力在 C++98 已有,后续标准放宽部分匹配规则
37 · 非类型及默认模板参数 用编译期值和默认参数配置模板;新标准扩展可接受的值类型 固定容量、编译期策略、减少常见调用参数 宏配置、重复特化或冗长实参列表 → 将配置纳入类型检查并提供默认值;传统 C++ 已支持其基础形式
38 · 模板实参推导 按实参推导模板参数,C++17 扩展到类模板构造 创建容器、包装器和泛型对象 重复写类型参数或依赖 make_* 工厂 → 降低调用噪声,减少声明与实参类型不同步
39 · 推导指引 C++17 显式规定构造实参应推导出哪个类模板实例 迭代器区间构造、自定义包装类型 要求调用者手填类型或额外提供工厂 → 将推导规则放在类型旁边,让构造接口保持简洁
40 · SFINAE 替换失败时排除候选,结合表达式检测选择重载 兼容旧标准的泛型库、检测类型操作能力 宏判断或大量显式特化 → 按类型能力选择实现;规则较隐晦,C++20 常可改用约束表达意图
41 · Concepts 与约束 C++20 用 concept / requires 命名并检查模板要求 公共泛型 API、按能力选择重载 enable_if、检测惯用法和间接替换失败 → 要求直接出现在接口上,约束可复用,诊断通常更接近调用原因
42 · 模板语法演进 对照嵌套 >>、变量模板及不同约束写法 迁移旧模板代码、封装编译期查询 尖括号间手工空格、trait<T>::value、复杂约束声明 → 减少语法噪声;具体支持版本应逐例核对

把语法放回使用场景

下面三组对照展示如何从“会写”进一步判断“为什么用”。

结构化绑定:接收多个返回值

场景:函数同时返回两个坐标,调用者需要分别命名并使用它们。

  • 原有写法:20-1.cpp 使用 C++11 的 tuple,先声明 int x, y,再用 std::tie(x, y) 接收结果。
  • 新写法:20-2.cpp 用 C++17 的 auto [x, y] = return_multiple_values(); 同时完成声明、类型推导与分解。
  • 好处:不用重复声明元素类型,也不用在后续代码中用 get<0>、get<1> 表示含义;主要收益是可读性,不应宣称必然更快。
  • 边界:结构化绑定会引入新名称;给已有变量赋值时,std::tie 仍适用。分解已有对象时,auto [x, y] = obj 通常会复制对象,需共享或修改原对象时应考虑引用形式。

移动语义:转移大对象的资源

场景:把一个不再需要的大缓冲区交给另一个对象。如果执行深拷贝,需要新分配内存并复制数据;类型提供移动操作后,可以转交资源所有权。

  • 语法作用:std::move(obj) 把表达式转换为可参与移动重载选择的形式,真正转移资源的是被选中的移动构造或移动赋值函数。
  • 好处:6-5.cpp 的移动构造只接管指针并清空源指针,相比其复制构造中的分配与 memcpy,可避免复制整个缓冲区。这个收益以允许转移所有权为前提。
  • 边界:同一文件的 get_pool(const BigMemoryPool& pool) 中,std::move(pool) 得到的是 const 右值,不能绑定到通常的 BigMemoryPool&& 移动构造参数,因此这里仍会调用复制构造。不能仅凭出现 std::move 就认定消除了拷贝。
  • 使用判断:仍需保留独立副本时应复制;只需只读访问时可传 const&。移动后源对象的可用操作取决于类型契约,不能假设值保持不变或一定为空。

Concepts:把模板要求写进接口

场景:模板只接受整数类型,希望调用处就能看清限制,并在传入不符合要求的类型时得到约束诊断。

  • 原有写法:41-1.cpp 用 enable_if_t 配合 is_integral_v 表达限制,把要求放进默认模板参数;这种机制源自 C++11,其中的 _t、_v 简写分别需要 C++14、C++17。
  • 新写法:41-2.cpp 在 C++20 中定义 IntegerType 概念,再用 template <IntegerType T> 直接约束参数。
  • 好处:约束有名称、可复用,接口更容易理解;不满足要求时,编译器通常能直接指出失败的约束。两者都是编译期机制,收益主要是接口表达和维护性。
  • 边界:约束只能验证写出的类型或表达式要求,不能证明业务正确性,例如一个比较器是否真的满足严格弱序;仍需实现者保证语义。

编译和运行

使用 CMake 构建已配置示例

根目录的 CMakeLists.txt 已升级为 C++20,统一收录了全部 157 个可独立编译运行的示例目标(排除了预期编译失败的反例及无 main() 的代码片段)。

目标命名规则为 ex<章节两位>_<序号两位>,例如 src/20-2.cpp 对应目标 ex20_02,同时兼容旧版别名(如 ex01, ex02-01 等):

# 1. 生成构建配置(必须使用独立构建目录)
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release

# 2. 一键编译全部 157 个示例目标
cmake --build build -j

# 3. 运行指定示例
./build/ex20_02
./build/ex06_05

单独编译任意示例

学习 src 时,推荐直接编译单个文件。C++20 可以覆盖多数示例使用的语言特性:

mkdir -p build/single
c++ -std=c++20 -Wall -Wextra src/29-2.cpp -o build/single/29-2
./build/single/29-2

涉及线程的示例通常还需要 -pthread:

c++ -std=c++20 -pthread src/25-3.cpp -o build/single/25-3
./build/single/25-3

阅读时的注意事项

  • 一些示例故意展示编译失败,例如错误的 override、失败的 static_assert 或不满足 Concepts 的类型。源码注释通常会标出“编译失败”或“编译错误”。
  • 一些文件是代码片段,不包含完整的头文件或 main();它们用于解释语法,不是独立程序。
  • 少数示例依赖编译器扩展、平台相关汇编或早期 <experimental/...> 头文件,现代编译器不一定继续提供对应接口。
  • 部分文件在注释中保留了汇编或 GIMPLE 输出,用于观察语言特性对生成代码的影响。
  • 遇到失败时,先判断它是教学反例、标准版本差异,还是平台/编译器扩展差异,再决定是否修改代码。

推荐阅读路线

  • 基础语法迁移:1 → 2 → 3 → 4 → 5 → 7 → 9 → 17 → 18 → 20。
  • 对象模型与性能:6 → 8 → 10 → 11 → 12 → 13 → 15 → 30 → 34。
  • 泛型编程:19 → 22 → 27 → 35 → 36 → 37 → 38 → 39 → 40 → 41 → 42。
  • C++20 重点:24 → 27 → 31 → 33 → 37 → 41 → 42。

About

现代C++

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages