# 现代 C++ 速览(C++11 / 14 / 17) > 适用对象:已掌握 C++ 基础语法与面向对象概念、正在向「现代 C++」迁移的学习者。 > 本仓库的编译基线是 **C++17**(见顶层 `CMakeLists.txt`),因此本文所有示例均可在 > `-std=c++17` 下直接编译。每个特性标注引入版本:`C++11` `C++14` `C++17`。 > > 配套材料:版本相关的「编译期」对照演示见 > [`docs/VERSION_NOTES.md`](../VERSION_NOTES.md) 与 `tools/demo_versions/`; > 本文聚焦「现代 C++ 是什么、为什么、怎么用」。 --- ## 0. 为什么把 C++11 叫做「分水岭」 在 2011 年之前,C++ 标准只有 C++98 和它的缺陷修复版 C++03。从 **C++11** 开始,标准 委员会改为**三年一个发布周期**,此后 C++14、C++17、C++20、C++23… 节奏稳定。 | 版本 | 年份 | 定位 | 一句话概括 | |---|---|---|---| | C++98/03 | 1998/2003 | 初代 + 修勘 | 经典 C++:手动内存管理、裸指针、深拷贝 | | **C++11** | 2011 | **大革命** | 几乎像一门新语言:auto、移动、智能指针、lambda、并发、模板增强 | | **C++14** | 2014 | 小完善 | 填坑:`make_unique`、泛型 lambda、`decltype(auto)` | | **C++17** | 2017 | 中等增量 | 实用大件:结构化绑定、`string_view`/`optional`/`variant`、`filesystem`、`if constexpr` | | C++20 | 2020 | 又一次大改 | 概念、范围、协程、模块(本文不展开) | **「现代 C++」的三个核心心智模型**(贯穿全文): 1. **RAII 优先**:资源 = 对象的生命周期,用析构函数自动释放,少写 `new`/`delete`。 2. **值类别与移动**:值不只是「拷贝」,还有「移动」——别再无谓地拷贝临时对象。 3. **编译期能做多少做多少**:`constexpr`、`static_assert`、类型推导,把工作前移。 --- ## 1. 类型推导:让代码少写类型名 ### 1.1 `auto` `C++11` ```cpp auto i = 42; // int auto& r = i; // int&(显式加 & 才保留引用) const auto& cr = i; // const int& auto p = std::make_pair(1, 2.0); // 不用写 std::pair for (auto& x : vec) { x *= 2; } // 范围 for(也是 C++11) ``` **核心概念**:`auto` 的推导规则**与模板参数推导完全一致**——会**丢弃顶层 const 和引用**。 所以要保留引用/const 必须显式写 `auto&` / `const auto&`。 **常见的坑**: - `auto x = expr;` 几乎总是「拷贝」。想要引用必须写 `&`,想要只读加 `const`。 - `auto&&` 在推导语境是「转发引用」(见 §3),不是固定右值引用。 - `auto` 不能用于数组、不能用于非静态成员的类型……推导不出就报错,通常很友好。 ### 1.2 `decltype` `C++11` ```cpp int x = 1; decltype(x) a; // int —— 变量名 → 该变量类型 decltype((x)) b; // int& —— 加括号 → 左值表达式 → 引用! ``` **核心概念**:`decltype(变量名)` 给类型;`decltype((变量名))`(**带括号**)给引用。这一对括号 的差异是最常见的考点。`decltype` 适合「我想要和某个表达式一模一样的类型,包括 const/引用」。 ### 1.3 返回类型推导 `C++14` ```cpp auto square(int x) { return x * x; } // C++14:返回类型由 return 推导 template auto add(T a, U b) { return a + b; } // 模板里尤其好用 ``` > C++11 只能写**尾随返回类型**:`auto f(int x) -> int { ... }`。C++14 起可直接 `auto`。 ### 1.4 `decltype(auto)` `C++14` ```cpp decltype(auto) f() { return some_expr; } // 用 decltype 规则推导,保留引用/const ``` 解决「`auto` 会丢引用」的场景,常用于转发包装器、属性 getter。 ### 1.5 结构化绑定 `C++17` ```cpp std::pair p = {1, std::string("hi")}; auto [id, name] = p; // id=1, name="hi" for (const auto& [key, val] : my_map) { // 遍历 map 终于不丑了 std::cout << key << " => " << val << "\n"; } struct Point { int x, y; }; auto [px, py] = Point{3, 4}; // 也能绑定公开成员 ``` **核心概念**:一次性把聚合/`pair`/`tuple`/数组的多个元素「拆」给若干变量,替代难看的 `std::get<0>(...)` 或 `.first/.second`。 --- ## 2. 移动语义与完美转发:别再无谓拷贝 ### 2.1 右值引用与移动构造 `C++11` ```cpp class Buffer { char* data_; size_t n_; public: Buffer(size_t n) : data_(new char[n]), n_(n) {} ~Buffer() { delete[] data_; } // 移动构造:窃取资源,把源对象置空 Buffer(Buffer&& o) noexcept : data_(o.data_), n_(o.n_) { o.data_ = nullptr; o.n_ = 0; } Buffer& operator=(Buffer&& o) noexcept { /* 类似 */ return *this; } // 拷贝(深拷贝)照常保留 Buffer(const Buffer& o) : data_(new char[o.n_]), n_(o.n_) { std::memcpy(data_, o.data_, n_); } }; ``` **核心概念**: - **左值 (lvalue)** 有名字、可取地址(如变量 `x`);**右值 (rvalue)** 是临时值(如 `Buffer(64)`、`std::move(x)`)。 - 右值引用 `T&&` 只绑定右值,表示「这个值马上要销毁,资源可以偷走」。 - 移动 = 「偷指针」而非「拷贝内容」,对 `vector`/`string`/`Buffer` 这类持堆资源的类型是巨大提速。 **`std::move` 的真相**:它**什么都不 move**,只是把左值**强制转换**成右值引用 (`static_cast`), 从而允许移动构造/赋值被选中。 ```cpp Buffer a(64); Buffer b = std::move(a); // 调用移动构造,a 变成空壳(仍需析构,但便宜) ``` **常见的坑**: - 移动后的对象处于「有效但未指定」状态,只能析构或重新赋值,不要继续读它。 - 移动构造/赋值**强烈建议标 `noexcept`**:否则 `vector` 扩容时为安全起见会退回拷贝。 - 不要对 `return x;` 里的局部变量写 `std::move(x)`——会**阻止**编译器的 RVO/隐式移动,反而更差。 ### 2.2 完美转发 `C++11` 让包装函数把参数「原封不动」(保留 lvalue/rvalue、const)转交给目标函数。三块基石: 1. **转发引用** `T&&`(仅在模板推导语境,绑定 lvalue 与 rvalue)。 2. **引用折叠**:`& + && = &`、`&& + && = &&` 等,让 `T` 编码了实参类别。 3. **`std::forward(x)`**:依据 `T` 把 `x` 还原成正确的值类别。 ```cpp template T my_make(Arg&& arg) { return T(std::forward(arg)); // 左值→拷贝,右值→移动,自动选对 } ``` **典型应用**:`std::make_unique` / `make_shared`、容器的 `emplace_back`(原地构造,省一次拷贝/移动)。 > 关于 `T&&` 何时是「转发引用」、何时是「纯右值引用」,详见 §10.4 与配套课件。 --- ## 3. 智能指针:RAII 的现代化 `C++11`(`make_unique` 为 `C++14`) 裸 `new`/`delete` 是现代 C++ 里**基本不该手写**的东西。三个智能指针各司其职: | 类型 | 语义 | 开销 | 典型用途 | |---|---|---|---| | `std::unique_ptr` | **独占**所有权,不可拷贝、可移动 | 几乎为零(默认等同裸指针) | **默认首选**;工厂返回、类成员持有资源 | | `std::shared_ptr` | **共享**所有权,引用计数 | 控制块 + 原子计数 | 多处需要共同拥有一同一对象、所有权不唯一时 | | `std::weak_ptr` | 观察 `shared_ptr`,不增加计数 | 一个指针 | 打破 `shared_ptr` 循环引用、缓存 | ```cpp // C++14:用 make_unique,异常安全、避免裸 new auto p = std::make_unique(args...); std::vector> shapes; shapes.push_back(std::make_unique(1.0)); shapes.push_back(std::make_unique(2.0)); auto shared = std::make_shared(); // 一次堆分配(对象+控制块) std::weak_ptr observer = shared; // 不延长生命周期 if (auto locked = observer.lock()) { /* 仍然存活时使用 */ } ``` **核心概念**:析构即释放——`unique_ptr` 出作用域自动 `delete`,`shared_ptr` 计数归零自动 `delete`。 异常发生时栈展开也会调用析构,**资源不泄漏**。这就是 RAII(Resource Acquisition Is Initialization)。 **常见的坑**: - `make_unique` 是 **C++14** 才有的;C++11 只能 `std::unique_ptr(new T(...))`。 - `shared_ptr` 的控制块是**每个对象一份**——用同一个裸指针构造两个 `shared_ptr` 会产生两个控制块,双重释放: ```cpp Widget* raw = new Widget; std::shared_ptr a(raw); // ❌ 危险 std::shared_ptr b(raw); // ❌ 两个独立计数 → double free ``` - `shared_ptr` 循环引用(A 持有 B,B 持有 A)会泄漏,一端改用 `weak_ptr` 打环。 - `shared_ptr` 线程安全**仅限引用计数操作**;指向的对象本身仍需另行同步。 > 历史:C++98 的 `std::auto_ptr` 因拷贝即「窃取」语义有坑,**C++17 已完全移除**。 --- ## 4. Lambda 表达式:就地写函数对象 ### 4.1 基本语法 `C++11` ```cpp [capture](params) mutable -> return_type { body } auto add = [](int a, int b) { return a + b; }; // 就地定义一个函数对象 add(1, 2); // 3 std::sort(v.begin(), v.end(), [](int a, int b) { return a > b; }); // 降序 int factor = 3; std::transform(v.begin(), v.end(), v.begin(), [factor](int x) { return x * factor; }); // 捕获 factor(按值) ``` **捕获方式**: - `[]` 不捕获;`[=]` 按值捕获所有用到的;`[&]` 按引用捕获所有用到的; - `[x]` 按值捕获 `x`;`[&x]` 按引用捕获 `x`;`[this]` 捕获当前对象指针(成员函数内常用); - `[=, &x]` 默认按值,但 `x` 按引用。 **核心概念**:lambda 本质是编译器生成的**匿名类(函数对象)**,捕获即成员变量。这让 STL 算法从「写个函数对象/函数指针」解脱出来,可以**就地表达**比较、变换、过滤逻辑。 **常见的坑**: - `[&]` 捕获引用时,lambda 比 captured 变量活得久(如存进 `std::function` 后异步用)→ 悬垂引用。 - 按值捕获默认使 lambda 为 `const`,要修改捕获的副本需加 `mutable`。 - 捕获 `this` 后,对象被销毁再调用 lambda → 悬垂。 ### 4.2 泛型 lambda `C++14` ```cpp auto plus = [](auto a, auto b) { return a + b; }; // 参数可用 auto plus(1, 2); // 3 plus(std::string("a"), std::string("b")); // "ab" ``` ### 4.3 `constexpr` lambda `C++17` ```cpp constexpr auto sq = [](int x) { return x * x; }; static_assert(sq(5) == 25); // 编译期可调用 ``` --- ## 5. 强类型与统一初始化 ### 5.1 `nullptr` 取代 `NULL` `C++11` ```cpp void f(int); void f(char*); f(0); // 调 f(int) —— 0 是 int! f(NULL); // 仍可能调 f(int)(NULL 的定义各不相同),坑 f(nullptr); // 一定调 f(char*) —— nullptr 是独立的 std::nullptr_t 类型 ``` **核心概念**:`nullptr` 是**带类型**的空指针字面量,彻底消除「`NULL` 在重载时被当成整数」的经典歧义。 ### 5.2 强类型枚举 `enum class` `C++11` ```cpp enum class Color { Red, Green, Blue }; // 不会污染外层作用域 Color c = Color::Red; // int i = c; // ❌ 不能隐式转 int(这正是想要的安全性) int i = static_cast(c); // ✅ 显式才行 enum OldColor { Red, Green }; // 旧式:Red/Green 泄漏到外层;可隐式转 int ``` **核心概念**:`enum class`(scoped enum)的作用域封闭、不隐式转换,比 C 风格 enum 安全得多。 还能指定底层类型:`enum class Color : uint8_t { ... };`。 ### 5.3 统一初始化(花括号)`C++11` ```cpp int a[]{1, 2, 3}; std::vector v{1, 2, 3, 4}; std::map m{{1, "a"}, {2, "b"}}; Point p{3, 4}; Widget w{}; // 默认构造 ``` **核心概念**:`{}` 是统一的初始化语法,适用于容器、聚合、普通对象;还能**窄化检查** (`int{3.14}` 编译失败,而 `int(3.14)` 截断通过)。 **常见的坑(most vexing parse)**: ```cpp Widget w(); // ❓ 被解析成「返回 Widget 的函数 w 的声明」! Widget w2{}; // ✅ 用花括号消除歧义,才是默认构造 ``` 花括号还能防窄化,但也引入新的歧义(与 `std::initializer_list` 构造函数的偏好),故现代风格里 **简单值用 `=`、聚合/容器用 `{}`**,二者并存。 --- ## 6. 类与面向对象的增强 | 特性 | 版本 | 作用 | |---|---|---| | `= default` / `= delete` | `C++11` | 显式要求/禁止某个特殊成员函数 | | `override` / `final` | `C++11` | 让编译器帮你检查虚函数重写、禁止进一步派生 | | 委托构造 | `C++11` | 一个构造函数调用同类的另一个构造函数 | | 继承构造 | `C++11`(C++17 调整语义) | `using Base::Base;` 复用基类构造函数 | | `inline` 变量 | `C++17` | 头文件里直接定义全局/静态变量,不再需要「声明+定义分离」 | ```cpp struct NonCopyable { NonCopyable() = default; NonCopyable(const NonCopyable&) = delete; // 禁止拷贝 NonCopyable& operator=(const NonCopyable&) = delete; }; struct Base { virtual void f(); virtual void g() final; // 子类不能再覆盖 g }; struct Derived : Base { void f() override; // ✅ 签名必须匹配,拼错或签名不符会编译报错 // void f() const override; // ❌ 与基类签名不符 → 编译错误(这正是 override 的价值) }; struct A { int x; A(int x) : x(x) {} A() : A(0) {} // 委托构造:调用 A(int) }; // C++17 inline 变量:头文件里安全地定义 struct Counter { inline static int instances = 0; // 不再是「声明」,所有 TU 共享一份 Counter() { ++instances; } }; ``` **核心概念**: - `override` 是**编译期契约**:你以为重写了,编译器没报错就一定是真重写了——杜绝「签名笔误导致隐藏而非重写」的隐蔽 bug。 - `= delete` 用**意图清晰的方式**禁用函数,比「声明为 private 且不实现」更直接。 - C++17 `inline` 变量终结了「头文件只能声明、定义放 cpp」的繁琐,对常量、单例尤其友好。 --- ## 7. 模板的增强 ### 7.1 可变参数模板 `C++11` ```cpp template void print(Args... args) { (std::cout << ... << args) << "\n"; // ← 这是 C++17 折叠表达式,见 7.4 } print(1, " + ", 2, " = ", 3); ``` `typename... Args` 是「模板参数包」,`Args... args` 是「函数参数包」。C++11 里展开包要用 递归模板,比较繁琐。 ### 7.2 别名模板 `using` `C++11` ```cpp template using Vec = std::vector; Vec v; // 等价 std::vector using StringList = std::vector; // 也取代 typedef,更直观 ``` `using` 比 `typedef` 强在能模板化,且读起来「左到右」更自然。 ### 7.3 `if constexpr` `C++17` ```cpp template auto get_value(T t) { if constexpr (std::is_pointer_v) return *t; // 仅当 T 是指针时才编译这一支 else return t; } ``` **核心概念**:`if constexpr` 在**编译期**按条件**丢弃**不成立的分支(不实例化)。 替代了以前必须用模板特化/SFINAE 才能实现的「按类型分支」。 ### 7.4 折叠表达式 `C++17` ```cpp template auto sum(Args... args) { return (args + ...); } // 一元右折叠 template auto sum2(Args... args) { return (0 + ... + args); } // 二元,带初值 sum(1, 2, 3, 4); // 10 ``` 让可变参数模板的「包展开」从递归套路变成一行,是 C++17 最受好评的小特性之一。 ### 7.5 类模板参数推导 CTAD `C++17` ```cpp std::pair p(1, 2.0); // 推导出 std::pair,无需写模板参数 std::vector v = {1, 2, 3}; // std::vector std::lock_guard lk(mtx); // 不用写 std::lock_guard ``` > 对 Qt 用户:`QSignalMapper` 之类的繁冗模板写法在 C++17 下可显著简化。 --- ## 8. `constexpr`:编译期计算 `constexpr` 表示「**可能在编译期求值**」。从 C++11 到 C++17 限制逐步放开: | 版本 | 能力 | |---|---| | `C++11` | 函数体**单条 return**;变量可用常量表达式初始化 | | `C++14` | 允许循环、局部变量、多语句、`if`/`switch` | | `C++17` | `if constexpr`;`constexpr` lambda;更多标准库函数(如 `std::array` 的部分操作)标记 constexpr | ```cpp constexpr int factorial(int n) { // C++14 起可写循环 int r = 1; for (int i = 2; i <= n; ++i) r *= i; return r; } int main() { constexpr int x = factorial(5); // 编译期求值 int arr[factorial(4)]; // 数组大小,编译期确定 static_assert(factorial(3) == 6); int n; std::cin >> n; int y = factorial(n); // 也可运行期调用(同一函数两用) } ``` **核心概念**:`constexpr` 函数「能在编译期就算编译期,不能就退回运行期」——同一份代码两用, 避免「一份宏/模板元编程 + 一份运行函数」的重复。 **`static_assert` `C++11`(无消息版本 `C++17`)**:编译期断言,失败即编译错误。 ```cpp static_assert(sizeof(int) >= 4, "需要 32 位 int"); static_assert(true); // C++17:可省略消息 ``` --- ## 9. 标准库的新组件 ### 9.1 容器与算法增强 | 组件 | 版本 | 要点 | |---|---|---| | `std::array` | `C++11` | 固定大小、栈上、零开销数组(比裸数组更安全、有迭代器) | | `std::tuple` | `C++11` | 异构类型打包;C++17 配结构化绑定拆解 | | `std::unordered_map`/`unordered_set` | `C++11` | 哈希表,O(1) 平均查找 | | `emplace` / `emplace_back` | `C++11` | 原地构造,省一次拷贝/移动(完美转发的直接受益者) | | `std::initializer_list` | `C++11` | 让 `{1,2,3}` 能作为构造/赋值参数 | | `std::move`(算法版)/ `std::move_backward` | `C++11` | 用移动而非拷贝在区间间搬运 | | 透明比较器 `std::less<>` | `C++14` | 异构查找,`map` 可用字符串字面量查而免构造 `string` | ```cpp std::map> m; // C++14 透明比较 m.find("key"); // 直接用 const char* 查,不构造临时 string ``` ### 9.2 C++17 的「实用三件套」+ 文件系统 ```cpp #include #include #include #include // optional:可能没有值 std::optional parse(std::string_view s) { int v{}; /* ... */ return v; // 有值 return std::nullopt; // 无值 } if (auto r = parse("42")) use(*r); // variant:类型安全的联合,替代 union std::variant v = "hello"; v = 3.14; std::visit([](auto&& x) { std::cout << x; }, v); // 配 lambda 模式匹配 // string_view:不拷贝的字符串「视图」 void log(std::string_view msg); // 接收 const char*、std::string、子串都不分配 // filesystem:跨平台文件操作(前身 boost::filesystem) namespace fs = std::filesystem; for (const auto& e : fs::directory_iterator("/tmp")) std::cout << e.path() << "\n"; fs::create_directories("a/b/c"); auto sz = fs::file_size("main.cpp"); ``` **核心概念**: - `optional`/`variant`/`any` 让「这个值可能不存在 / 可能是多种类型之一 / 任意类型」**进入类型系统**, 编译器帮你保证访问安全,取代裸指针 + 约定、`union` + tag 的脆弱写法。 - `string_view` 是**只读视图**,构造零开销——适合函数参数;但**不拥有**数据,调用方必须保证源串存活。 - `filesystem` 让文件操作不再依赖 ``/``,一份代码跨平台。 **常见的坑**: - `string_view` 不保证以 `\0` 结尾,别直接传给需要 C 字符串的 API(要用 `.data()` 且确认有终止符)。 - `variant` 默认构造为**第一个备选类型**的值,访问时类型不对会抛 `std::bad_variant_access`(或用 `std::get_if`)。 ### 9.3 并发支持 | 组件 | 版本 | 作用 | |---|---|---| | `std::thread` | `C++11` | 跨平台线程 | | `std::mutex` / `std::lock_guard` / `std::unique_lock` | `C++11` | 互斥与 RAII 加锁 | | `std::async` / `std::future` / `std::promise` | `C++11` | 异步任务与结果传递 | | `std::atomic` | `C++11` | 无锁原子操作 | | `std::condition_variable` | `C++11` | 线程间等待/通知 | | `std::shared_mutex`(读写锁) | `C++14` | 多读单写 | | `std::scoped_lock`(一次锁多个) | `C++17` | 彻底避免死锁的统一加锁 | ```cpp std::mutex m; int counter = 0; std::vector ts; for (int i = 0; i < 8; ++i) ts.emplace_back([&]{ std::lock_guard lk(m); // RAII:构造加锁、析构解锁 ++counter; }); for (auto& t : ts) t.join(); ``` > 注意:C++ 标准库的并发模型较底层。Qt 方向课程里更常用 `QThread` + 信号槽的 worker 模式, > 两者可对照学习——见 `docs/teaching/examples/qthread_worker/`。 --- ## 10. 其它实用语言特性 ### 10.1 属性 `[[...]]` | 属性 | 版本 | 作用 | |---|---|---| | `[[noreturn]]` | `C++11` | 标注函数永不返回(如 `abort`、死循环) | | `[[deprecated]]` | `C++14` | 标记弃用,使用时编译警告 | | `[[deprecated("用 newFunc")]]` | `C++14` | 带提示信息 | | `[[fallthrough]]` | `C++17` | switch 显式贯穿(抑制警告) | | `[[nodiscard]]` | `C++17` | 返回值不可忽略(如 `malloc`、工厂函数) | | `[[maybe_unused]]` | `C++17` | 抑制「未使用」警告 | ```cpp [[nodiscard]] int* alloc(); auto p = alloc(); // 不接收 → 警告,提醒你检查是否泄漏 switch (n) { case 1: prepare(); [[fallthrough]]; // 我是故意贯穿的,别警告 case 2: run(); } ``` ### 10.2 `noexcept` `C++11` ```cpp void swap(T& a, T& b) noexcept; // 承诺不抛异常 ``` **核心概念**:`noexcept` 是函数**接口的一部分**——它让调用者(尤其是 STL 容器)敢于用移动 而非拷贝。移动构造/赋值加 `noexcept` 对性能**至关重要**。`noexcept(expr)` 还能按条件判断: `void f() noexcept(noexcept(g()));`(f 是否不抛取决于 g)。 > 旧式动态异常规范 `throw(int, char)` 在 C++11 弃用、**C++17 移除**——见 > `docs/VERSION_NOTES.md` 第 6 节与 `tools/demo_versions/dynamic_exception_spec.cpp`。 ### 10.3 字符串与字面量 `C++11` - **`std::to_string` / `std::stoi/stof/stod`**:数值 ↔ 字符串互转。 - **原始字符串字面量** `R"(...)"`:反斜杠、引号都原样保留,写正则/路径/HTML 不再「转义地狱」。 ```cpp std::regex re(R"(\d+\.\d+)"); // 等价 "\\d+\\.\\d+" std::string path = R"(C:\Users\me)"; // 反斜杠原样 ``` - **用户定义字面量 (UDL)**:`"hello"s` 是 `std::string`,`1h`/`1ms` 是 `std::chrono::duration`。 ```cpp using namespace std::string_literals; auto s = "hi"s + ", world"; // string + 拼接 using namespace std::chrono_literals; std::this_thread::sleep_for(200ms); ``` ### 10.4 杂项但常用 | 特性 | 版本 | 备注 | |---|---|---| | 范围 for | `C++11` | `for (auto& x : c)` | | `long long` / `char16_t` / `char32_t` | `C++11` | 标准化的 64 位整型、UTF-16/32 字符 | | `alignas` / `alignof` | `C++11` | 对齐控制与查询 | | `thread_local` | `C++11` | 线程局部存储 | | `using` 别名(取代 `typedef`) | `C++11` | 见 §7.2 | | 变量模板 | `C++14` | `template constexpr T pi = T(3.14159265);` | --- ## 附 A:C++11/14/17 速查表 ``` 类型推导 auto(11) decltype(11) 返回auto(14) decltype(auto)(14) 结构化绑定(17) 移动语义 右值引用(11) std::move(11) std::forward(11) 完美转发(11) 资源管理 unique_ptr/shared_ptr/weak_ptr(11) make_unique(14) RAII Lambda 基础(11) 泛型(14) constexpr(17) 强类型 nullptr(11) enum class(11) 统一初始化(11) 类增强 =default/=delete(11) override/final(11) 委托构造(11) inline变量(17) 模板增强 可变参数(11) using别名(11) 变量模板(14) if constexpr(17) 折叠(17) CTAD(17) 编译期 constexpr(11/14/17) static_assert(11, 无消息17) 标准库 array/tuple/unordered(11) emplace(11) 透明比较(14) optional/variant/any/string_view/filesystem/并行算法(17) 并发 thread/mutex/future/atomic(11) shared_mutex(14) scoped_lock(17) 杂项 范围for/raw string/UDL/noexcept/属性[[...]](11) ``` ## 附 B:学习路径建议(给课堂学员) 1. **先建立心智模型**:RAII、移动语义、值类别——这三者理解了,剩下都是语法。 2. **第一周可上手**:`auto`、范围 for、`nullptr`、`enum class`、智能指针、lambda—— 立刻让日常代码更短更安全。 3. **进阶**:完美转发、`constexpr`、模板增强(`if constexpr`/折叠/CTAD)。 4. **C++17 标配库**:`string_view`/`optional`/`variant`/`filesystem` 是日常工程刚需, 写 Qt 程序时同样适用。 5. **避坑读物**:Scott Meyers《Effective Modern C++》、cppreference.com(查权威细节)。 ## 附 C:与本仓库的关联 - **编译基线**:所有示例在 `-std=c++17` 下编译(顶层 `CMakeLists.txt`)。 - **跨版本验证**:想知道某特性在 C++11/14/17 下分别能否编译,用 `tools/std_probe.py`(例:动态异常规范在 C++17 失败,见 `docs/VERSION_NOTES.md`)。 - **Qt 侧**:本仓库 `p03/` 与 `docs/teaching/examples/` 大量使用了上述现代特性 (智能指针管理 Qt 对象生命周期、lambda 连信号槽、`QString` 与 `string_view` 互转等)。 - **下一步**:若希望把本文档的某个特性做成可编译运行的 `tools/demo_versions/` 最小示例, 请明确提出,按项目规范(生成器 / errata 流程)补齐。