- 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 (大纲与幻灯片多处链接它们)
26 KiB
现代 C++ 速览(C++11 / 14 / 17)
适用对象:已掌握 C++ 基础语法与面向对象概念、正在向「现代 C++」迁移的学习者。 本仓库的编译基线是 C++17(见顶层
CMakeLists.txt),因此本文所有示例均可在-std=c++17下直接编译。每个特性标注引入版本:C++11C++14C++17。配套材料:版本相关的「编译期」对照演示见
docs/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++」的三个核心心智模型(贯穿全文):
- RAII 优先:资源 = 对象的生命周期,用析构函数自动释放,少写
new/delete。 - 值类别与移动:值不只是「拷贝」,还有「移动」——别再无谓地拷贝临时对象。
- 编译期能做多少做多少:
constexpr、static_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)转交给目标函数。三块基石:
- 转发引用
T&&(仅在模板推导语境,绑定 lvalue 与 rvalue)。 - 引用折叠:
& + && = &、&& + && = &&等,让T编码了实参类别。 std::forward<T>(x):依据T把x还原成正确的值类别。
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++11(make_unique 为 C++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 出作用域自动 delete,shared_ptr 计数归零自动 delete。
异常发生时栈展开也会调用析构,资源不泄漏。这就是 RAII(Resource Acquisition Is Initialization)。
常见的坑:
make_unique是 C++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 freeshared_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 class(scoped 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++11(C++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,更直观
using 比 typedef 强在能模板化,且读起来「左到右」更自然。
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 constexpr;constexpr 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"s是std::string,1h/1ms是std::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); |
附 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:学习路径建议(给课堂学员)
- 先建立心智模型:RAII、移动语义、值类别——这三者理解了,剩下都是语法。
- 第一周可上手:
auto、范围 for、nullptr、enum class、智能指针、lambda—— 立刻让日常代码更短更安全。 - 进阶:完美转发、
constexpr、模板增强(if constexpr/折叠/CTAD)。 - C++17 标配库:
string_view/optional/variant/filesystem是日常工程刚需, 写 Qt 程序时同样适用。 - 避坑读物: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 流程)补齐。