本仓库收集了 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 章,主题是移动语义和完美转发。同一章通常通过一组相近代码展示正确写法、错误写法及版本差异。
- 从下方章节索引定位感兴趣的语言特性。
- 先读该章的语法作用和使用场景,再打开源码入口,识别
main()、核心类型和关键表达式。 - 按编号比较同章示例:原有写法有什么成本,新特性减少了哪些重复、错误或资源开销?
- 单独编译并运行一个示例,不要直接假设整章都能批量构建。
- 修改输入、类型或限定符,再观察编译结果和输出。
以下命令适合快速查看和比较一章代码:
# 列出第 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&。移动后源对象的可用操作取决于类型契约,不能假设值保持不变或一定为空。
场景:模板只接受整数类型,希望调用处就能看清限制,并在传入不符合要求的类型时得到约束诊断。
- 原有写法: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>直接约束参数。 - 好处:约束有名称、可复用,接口更容易理解;不满足要求时,编译器通常能直接指出失败的约束。两者都是编译期机制,收益主要是接口表达和维护性。
- 边界:约束只能验证写出的类型或表达式要求,不能证明业务正确性,例如一个比较器是否真的满足严格弱序;仍需实现者保证语义。
根目录的 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。