Files
scuc-qt-course/docs/teaching/MODERN_CPP.md
T
张宗平 1053ebf41c 教案基准切换:新版 10 天安排 + 4 天版大纲(含中初级误区审计)
- docs/SCHEDULE_2026_10DAY.md:2026-07-05 新版 10 天安排正式文本,
  取代 TRAINING_PLAN_2026.md 旧执行计划作为课堂设计基准
- docs/teaching/OUTLINE_4DAY.md:4 天讲授 + Day5 综合案例的正式教案大纲,
  含逐天设计、旧主题去向决策(QSS→Day3、JSON→Day4、QThread→Day5 提升)、
  中初级学员误区盲点审计(10 张新坑卡:所有权/编码路径/连接语义三主线)、
  项目就绪度与高阶兴趣钩子评估
- OUTLINE.md 顶部加基准变更横幅,5 天版保留作历史参照与回退预案
- 顺带收编两篇此前未跟踪的自学文档 MODERN_CPP.md / MODERN_CPP_20PLUS.md
  (大纲与幻灯片多处链接它们)
2026-07-05 12:49:32 +08:00

26 KiB
Raw Blame History

现代 C++ 速览(C++11 / 14 / 17

适用对象:已掌握 C++ 基础语法与面向对象概念、正在向「现代 C++」迁移的学习者。 本仓库的编译基线是 C++17(见顶层 CMakeLists.txt),因此本文所有示例均可在 -std=c++17 下直接编译。每个特性标注引入版本:C++11 C++14 C++17

配套材料:版本相关的「编译期」对照演示见 docs/VERSION_NOTES.mdtools/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/variantfilesystemif constexpr
C++20 2020 又一次大改 概念、范围、协程、模块(本文不展开)

「现代 C++」的三个核心心智模型(贯穿全文):

  1. RAII 优先:资源 = 对象的生命周期,用析构函数自动释放,少写 new/delete
  2. 值类别与移动:值不只是「拷贝」,还有「移动」——别再无谓地拷贝临时对象。
  3. 编译期能做多少做多少constexprstatic_assert、类型推导,把工作前移。

1. 类型推导:让代码少写类型名

1.1 auto C++11

auto i = 42;                 // int
auto& r = i;                 // int&(显式加 & 才保留引用)
const auto& cr = i;          // const int&
auto p = std::make_pair(1, 2.0);  // 不用写 std::pair<int,double>

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

int x = 1;
decltype(x)   a;   // int   —— 变量名 → 该变量类型
decltype((x)) b;   // int&  —— 加括号 → 左值表达式 → 引用!

核心概念decltype(变量名) 给类型;decltype((变量名))带括号)给引用。这一对括号 的差异是最常见的考点。decltype 适合「我想要和某个表达式一模一样的类型,包括 const/引用」。

1.3 返回类型推导 C++14

auto square(int x) { return x * x; }     // C++14:返回类型由 return 推导

template<typename T, typename U>
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

decltype(auto) f() { return some_expr; }  // 用 decltype 规则推导,保留引用/const

解决「auto 会丢引用」的场景,常用于转发包装器、属性 getter。

1.5 结构化绑定 C++17

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

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<T&&>) 从而允许移动构造/赋值被选中。

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<T>(x):依据 Tx 还原成正确的值类别。
template<typename T, typename Arg>
T my_make(Arg&& arg) {
    return T(std::forward<Arg>(arg));   // 左值→拷贝,右值→移动,自动选对
}

典型应用std::make_unique / make_shared、容器的 emplace_back(原地构造,省一次拷贝/移动)。

关于 T&& 何时是「转发引用」、何时是「纯右值引用」,详见 §10.4 与配套课件。


3. 智能指针:RAII 的现代化 C++11make_uniqueC++14

new/delete 是现代 C++ 里基本不该手写的东西。三个智能指针各司其职:

类型 语义 开销 典型用途
std::unique_ptr<T> 独占所有权,不可拷贝、可移动 几乎为零(默认等同裸指针) 默认首选;工厂返回、类成员持有资源
std::shared_ptr<T> 共享所有权,引用计数 控制块 + 原子计数 多处需要共同拥有一同一对象、所有权不唯一时
std::weak_ptr<T> 观察 shared_ptr,不增加计数 一个指针 打破 shared_ptr 循环引用、缓存
// C++14:用 make_unique,异常安全、避免裸 new
auto p = std::make_unique<Widget>(args...);

std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Circle>(1.0));
shapes.push_back(std::make_unique<Square>(2.0));

auto shared = std::make_shared<Widget>();   // 一次堆分配(对象+控制块)
std::weak_ptr<Widget> observer = shared;    // 不延长生命周期
if (auto locked = observer.lock()) { /* 仍然存活时使用 */ }

核心概念:析构即释放——unique_ptr 出作用域自动 deleteshared_ptr 计数归零自动 delete。 异常发生时栈展开也会调用析构,资源不泄漏。这就是 RAIIResource Acquisition Is Initialization)。

常见的坑

  • make_uniqueC++14 才有的;C++11 只能 std::unique_ptr<T>(new T(...))
  • shared_ptr 的控制块是每个对象一份——用同一个裸指针构造两个 shared_ptr 会产生两个控制块,双重释放:
    Widget* raw = new Widget;
    std::shared_ptr<Widget> a(raw);   // ❌ 危险
    std::shared_ptr<Widget> 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

[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

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

constexpr auto sq = [](int x) { return x * x; };
static_assert(sq(5) == 25);   // 编译期可调用

5. 强类型与统一初始化

5.1 nullptr 取代 NULL C++11

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

enum class Color { Red, Green, Blue };   // 不会污染外层作用域
Color c = Color::Red;
// int i = c;          // ❌ 不能隐式转 int(这正是想要的安全性)
int i = static_cast<int>(c);   // ✅ 显式才行

enum OldColor { Red, Green };  // 旧式:Red/Green 泄漏到外层;可隐式转 int

核心概念enum classscoped enum)的作用域封闭、不隐式转换,比 C 风格 enum 安全得多。 还能指定底层类型:enum class Color : uint8_t { ... };

5.3 统一初始化(花括号)C++11

int a[]{1, 2, 3};
std::vector<int> v{1, 2, 3, 4};
std::map<int, std::string> m{{1, "a"}, {2, "b"}};
Point p{3, 4};
Widget w{};                 // 默认构造

核心概念{} 是统一的初始化语法,适用于容器、聚合、普通对象;还能窄化检查 int{3.14} 编译失败,而 int(3.14) 截断通过)。

常见的坑(most vexing parse

Widget w();     // ❓ 被解析成「返回 Widget 的函数 w 的声明」!
Widget w2{};    // ✅ 用花括号消除歧义,才是默认构造

花括号还能防窄化,但也引入新的歧义(与 std::initializer_list 构造函数的偏好),故现代风格里 简单值用 =、聚合/容器用 {},二者并存。


6. 类与面向对象的增强

特性 版本 作用
= default / = delete C++11 显式要求/禁止某个特殊成员函数
override / final C++11 让编译器帮你检查虚函数重写、禁止进一步派生
委托构造 C++11 一个构造函数调用同类的另一个构造函数
继承构造 C++11C++17 调整语义) using Base::Base; 复用基类构造函数
inline 变量 C++17 头文件里直接定义全局/静态变量,不再需要「声明+定义分离」
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

template<typename... Args>
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

template<typename T> using Vec = std::vector<T>;
Vec<int> v;                          // 等价 std::vector<int>

using StringList = std::vector<std::string>;   // 也取代 typedef,更直观

usingtypedef 强在能模板化,且读起来「左到右」更自然。

7.3 if constexpr C++17

template<typename T>
auto get_value(T t) {
    if constexpr (std::is_pointer_v<T>)
        return *t;          // 仅当 T 是指针时才编译这一支
    else
        return t;
}

核心概念if constexpr编译期按条件丢弃不成立的分支(不实例化)。 替代了以前必须用模板特化/SFINAE 才能实现的「按类型分支」。

7.4 折叠表达式 C++17

template<typename... Args>
auto sum(Args... args) { return (args + ...); }          // 一元右折叠
template<typename... Args>
auto sum2(Args... args) { return (0 + ... + args); }     // 二元,带初值

sum(1, 2, 3, 4);   // 10

让可变参数模板的「包展开」从递归套路变成一行,是 C++17 最受好评的小特性之一。

7.5 类模板参数推导 CTAD C++17

std::pair p(1, 2.0);          // 推导出 std::pair<int, double>,无需写模板参数
std::vector v = {1, 2, 3};    // std::vector<int>
std::lock_guard lk(mtx);      // 不用写 std::lock_guard<std::mutex>

对 Qt 用户:QSignalMapper 之类的繁冗模板写法在 C++17 下可显著简化。


8. constexpr:编译期计算

constexpr 表示「可能在编译期求值」。从 C++11 到 C++17 限制逐步放开:

版本 能力
C++11 函数体单条 return;变量可用常量表达式初始化
C++14 允许循环、局部变量、多语句、if/switch
C++17 if constexprconstexpr lambda;更多标准库函数(如 std::array 的部分操作)标记 constexpr
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:编译期断言,失败即编译错误。

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,int> 可用字符串字面量查而免构造 string
std::map<std::string, int, std::less<>> m;   // C++14 透明比较
m.find("key");   // 直接用 const char* 查,不构造临时 string

9.2 C++17 的「实用三件套」+ 文件系统

#include <optional>
#include <variant>
#include <string_view>
#include <filesystem>

// optional:可能没有值
std::optional<int> parse(std::string_view s) {
    int v{};
    /* ... */
    return v;            // 有值
    return std::nullopt; // 无值
}
if (auto r = parse("42")) use(*r);

// variant:类型安全的联合,替代 union
std::variant<int, double, std::string> 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 让文件操作不再依赖 <dirent.h>/<windows.h>,一份代码跨平台。

常见的坑

  • 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<T> C++11 无锁原子操作
std::condition_variable C++11 线程间等待/通知
std::shared_mutex(读写锁) C++14 多读单写
std::scoped_lock(一次锁多个) C++17 彻底避免死锁的统一加锁
std::mutex m;
int counter = 0;
std::vector<std::thread> ts;
for (int i = 0; i < 8; ++i)
    ts.emplace_back([&]{
        std::lock_guard<std::mutex> 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 抑制「未使用」警告
[[nodiscard]] int* alloc();
auto p = alloc();          // 不接收 → 警告,提醒你检查是否泄漏

switch (n) {
case 1: prepare();
    [[fallthrough]];       // 我是故意贯穿的,别警告
case 2: run();
}

10.2 noexcept C++11

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 不再「转义地狱」。
    std::regex re(R"(\d+\.\d+)");         // 等价 "\\d+\\.\\d+"
    std::string path = R"(C:\Users\me)";  // 反斜杠原样
    
  • 用户定义字面量 (UDL)"hello"sstd::string1h/1msstd::chrono::duration
    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<typename T> constexpr T pi = T(3.14159265);

附 AC++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、nullptrenum 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 连信号槽、QStringstring_view 互转等)。
  • 下一步:若希望把本文档的某个特性做成可编译运行的 tools/demo_versions/ 最小示例, 请明确提出,按项目规范(生成器 / errata 流程)补齐。