目录
从 C++98 到 C++20:为什么要学现代 C++ 右值引用与 move 语义 std::move与 std::forward:移动与完美转发移动构造/移动赋值与"被移动的对象的契约" 值类别:左值、右值、纯右值、将亡值 auto与 decltype类型推导范围 for 与初始化列表 lambda 表达式:闭包的本质 nullptr、强类型枚举、 constexpr 模板基础:函数模板、类模板、特化 偏特化与 std::enable_if/ SFINAECRTP:静态多态 可变参数模板与折叠表达式 C++20 Concepts:模板约束的救世主 面试真题与速记
1. 从 C++98 到 C++20:为什么要学现代 C++
C++11 是"C++ 的第二语言",引入 auto、lambda、右值引用、智能指针、 constexpr、线程库、nullptr 等;C++14 完善泛型 lambda、make_unique;C++17 加入 std::optional/variant/string_view/filesystem/if constexpr/structured bindings;C++20 带来 concepts、协程、modules、ranges。现代 C++ 目标是:资源管理自动化(RAII + 智能指针)、零成本抽象、表达力强、少写样板。面试中能熟练使用现代特性并讲清底层原理,是高级岗的硬指标。
2. 右值引用与 move 语义
左值(lvalue):有名字、有地址、可取 & 的表达式(如变量 int x;、*p)。右值(rvalue):临时对象、字面量、将亡值,无持久身份(如 42、func() 返回值、x+y)。
右值引用 T&&:只能绑定到右值,是 move 语义的基础。
为什么需要 move? 深拷贝大对象(如 std::vector 扩容、函数返回大对象)代价高。move 是"偷"资源(转移指针)而非复制,代价 O(1)。
#include<vector>#include<iostream>std::vector<int> make(){std::vector<int> v(1000000);return v; // C++11 起:NRVO/move,不深拷贝}intmain(){std::vector<int> a = make(); // 移动构造(偷内部缓冲区)}
关键:std::vector 的移动构造只复制三个指针(指向数据、大小、容量)并把源对象置空,O(1) 而非 O(n)。
3. std::move 与 std::forward:移动与完美转发
std::move:只是一个强制类型转换,把左值/右值变成右值引用( static_cast<T&&>),本身不移动任何东西。真正的"移动"发生在移动构造/赋值里。std::forward<T>:完美转发,在模板中按原值类别(左值仍是左值、右值仍是右值)转发参数。
#include<utility>#include<string>voidprocess(std::string&& s){ /* 接管 s 的资源 */ }template <typename T>voidrelay(T&& arg){ // 通用引用(universal reference)process(std::forward<T>(arg)); // 保持 arg 原本的左/右值属性}intmain(){std::string a = "hello";relay(a); // T=string&,转发为左值relay(std::string("tmp")); // T=string,转发为右值}
通用引用(universal/forwarding reference):T&& 出现在类型推导上下文(模板参数或 auto&&)时,既能绑左值也能绑右值,左值实例化出 T&,右值实例化出 T&&。这是完美转发的前提。
经典面试题:std::move 之后对象处于什么状态?答:处于"有效但未指定(valid but unspecified)"状态——可以析构或赋值,但不应再使用其值(除非重新赋值)。标准只保证可安全销毁/赋值。
4. 移动构造/移动赋值与契约
#include<cstring>class Buffer {char* data; size_t len;public:Buffer(Buffer&& o) noexcept // 移动构造: data(o.data), len(o.len) {o.data = nullptr; o.len = 0; // 源置空,防止析构时 double free}Buffer& operator=(Buffer&& o) noexcept { // 移动赋值if (this != &o) {delete[] data; // 释放自己的旧资源data = o.data; len = o.len;o.data = nullptr; o.len = 0;}return *this;}~Buffer() { delete[] data; }};
重点:
移动操作应标记为 noexcept,否则std::vector在扩容时因怕移动抛异常会退化为拷贝(影响性能)。移动后析构必须安全(源已置空)。 编译器生成的移动操作:若用户定义了析构/拷贝/赋值,则不自动生成移动操作(经典陷阱)。
5. 值类别精解
C++11 把表达式按"身份(identity)+ 可移动性"分类:
- lvalue
:有身份、不可移动(可被取地址)。 - xvalue(将亡值)
:有身份、可移动(如 std::move(x)、static_cast<T&&>(x))。 - prvalue(纯右值)
:无身份、可移动(临时对象、 42、a+b结果)。 - glvalue
= lvalue + xvalue;rvalue = prvalue + xvalue。
理解这张表是答好"值类别"题的关键。std::move 把 lvalue/xvalue 变成 xvalue;std::forward 保持类别。
6. auto 与 decltype 类型推导
auto:从初始化表达式推导(忽略引用/顶层 const,除非 auto&/const auto&)。decltype(expr):返回 expr的声明类型,保留引用和 const。decltype(auto):结合两者,完美保留类别(用于转发返回)。
int x = 0;int& r = x;auto a = r; // a 是 int(引用被脱掉)decltype(r) b = x; // b 是 int&(保留引用)decltype(auto) c = r;// c 是 int&
注意坑:auto 在 std::vector<bool> 上是特化,返回代理对象而非 bool&,别踩。范围 for 用 auto& 避免拷贝;只读用 const auto&;要移动用 auto&&。
7. 范围 for 与初始化列表
std::vector<int> v = {1, 2, 3}; // 初始化列表for (const auto& x : v) std::cout << x; // 范围 for
范围 for 底层等价于 auto it = begin(v); ... *it; ++it;,因此自定义类型只要提供 begin()/end()(返回迭代器)即可用。
8. lambda 表达式:闭包的本质
lambda 是匿名函数对象(闭包),编译器生成一个带 operator() 的匿名类。捕获列表决定它"吸"了哪些变量。
int base = 10;auto f = [base](int x) mutable { return x + base++; }; // 值捕获(拷贝),mutable 才能改副本auto g = [&base](int x) { return x + base; }; // 引用捕获// 泛型 lambda(C++14):参数用 autoauto h = [](auto a, auto b) { return a + b; };// 完美转发捕获(C++14 用 init-capture)auto p = [y = std::move(some_str)]() { return y; };
捕获方式:
[]:不捕获; [=]:值捕获全部;[&]:引用捕获全部。[this]:捕获 this 指针(注意悬空!lambda 活得比对象久会 UB)。 推荐显式列出要捕获的变量,避免 [&]误引用已销毁局部变量。
与 std::function:lambda 可隐式转 std::function(类型擦除,有开销);作为模板参数直接传 lambda 类型零开销。std::function 本质是堆分配的小对象 + 虚调用,高频路径慎用。
9. nullptr、强类型枚举、constexpr
nullptr:类型安全的空指针,类型是 std::nullptr_t,解决NULL/0在重载歧义(如void f(int)vsvoid f(char*)传NULL可能选int)。enum class(强类型枚举):作用域限定( Color::Red)、不隐式转 int、可指定底层类型,避免污染与歧义。constexpr:编译期求值。 constexpr函数若参数是编译期常量,则结果在编译期算出(如数组大小、模板参数)。C++14 起constexpr函数体内可有循环/变量。
constexpr int sq(int x) { return x * x; }static_assert(sq(5) == 25, ""); // 编译期int arr[sq(3)]; // 用作数组大小
const vs constexpr:const 是运行期只读;constexpr 是编译期常量(隐含 const)。面试能区分两者即过关。
10. 模板基础:函数模板、类模板、特化
模板是 C++ 泛型与零成本抽象的核心。
template <typename T>T max(T a, T b){ return a > b ? a : b; } // 函数模板template <typename T, size_t N>class Array { T data_[N]; }; // 类模板// 全特化:针对特定类型定制template <>class Array<bool, 8> { /* 位压缩实现 */ };
模板实例化:编译器按使用的具体类型生成代码(代码膨胀风险)。typename 在嵌套依赖类型前必须写(如 typename T::value_type),这是语法要求而非风格。
11. 偏特化与 std::enable_if / SFINAE
偏特化(partial specialization):类模板可针对部分参数特化(函数模板不支持偏特化,用重载/enable_if 解决)。
SFINAE(Substitution Failure Is Not An Error):模板参数替换失败不算错误,只是该重载被剔除。这是编译期"类型分发"的经典技术。
#include<type_traits>#include<iostream>// 仅当 T 是整型才启用template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>void process(T v) { std::cout << "int: " << v << "\n"; }// 仅当 T 是浮点template <typename T, typename = std::enable_if_t<std::is_floating_point_v<T>>>void process(T v) { std::cout << "float: " << v << "\n"; }
注意上面两个函数模板"第二个默认参数相同"会冲突,实际要用 std::enable_if 放在返回类型或额外模板参数上区分。正确写法:
template <typename T>std::enable_if_t<std::is_integral_v<T>> process(T v) { /* 整型版 */ }template <typename T>std::enable_if_t<std::is_floating_point_v<T>> process(T v) { /* 浮点版 */ }
面试意义:老派"编译期条件分支"全靠 SFINAE,理解它能读懂 STL/Boost 源码。但 C++20 推荐用 concepts 替代(更清晰)。
12. CRTP:静态多态
CRTP(Curiously Recurring Template Pattern):基类以派生类为模板参数,实现编译期多态,避免虚函数运行时开销。
template <typename Derived>struct Base {voidinterface() {static_cast<Derived*>(this)->impl(); // 静态分发,无虚表}};struct D : Base<D> {voidimpl() { /* 具体实现 */ }};
应用:std::enable_shared_from_this<T>、boost::iterator_facade、Eigen 表达式模板、计数对象个数(Counter<D>)、静态接口约束。面试能说出"编译期多态替代虚函数、无虚表开销"即加分。
13. 可变参数模板与折叠表达式
C++11 可变参数模板让函数接受任意数量/类型参数,配合递归展开或 ... 展开。C++17 折叠表达式更优雅。
#include<iostream>// C++17 折叠表达式:打印所有参数template <typename... Args>voidprint(Args&&... args){(std::cout << ... << args) << "\n"; // 一元左折}// 求和template <typename... Nums>autosum(Nums... n){ return (n + ...); } // 折叠intmain(){ print(1, " + ", 2, " = ", 3); }
std::forward + 可变参数 实现完美转发工厂:
template <typename T, typename... Args>std::unique_ptr<T> make_unique(Args&&... args){return std::unique_ptr<T>(new T(std::forward<Args>(args)...));}
应用:std::tuple、std::make_shared、fmt/std::format、日志库变参接口。能手写递归展开(基例 + 递归)是面试常考。
14. C++20 Concepts:模板约束的救世主
Concepts 让模板约束可读、报错友好,取代晦涩的 SFINAE。
#include<concepts>template <std::integral T> // T 必须整数T add(T a, T b){ return a + b; }template <typename T>concept Addable = requires(T a, T b) { a + b; }; // 自定义概念template <Addable T>T sum(T a, T b){ return a + b; }
对比 SFINAE:旧代码约束一长串 enable_if 嵌套,报错如同天书;concepts 直接在接口处写约束,编译错误精准指向"类型不满足概念"。面试若能在现代特性题里展示 concepts 优先于 SFINAE,体现对 C++20 的掌握。
requires 表达式还能约束表达式的有效性、异常规范、返回类型(配合 std::same_as 等),实现精细接口契约。
15. 面试真题与速记
速记清单:
右值引用 T&&支撑 move;std::move仅转换不移动,move 发生在构造/赋值。std::forward完美转发,配合通用引用 T&&(推导上下文)。move 操作标 noexcept;移动后源对象"有效但未指定"。值类别:lvalue / xvalue / prvalue; glvalue、rvalue是其并集。auto脱引用/顶层 const; decltype保留类别。lambda = 闭包类; [=]值、[&]引用;[this]注意悬空。nullptr类型安全; enum class强类型;constexpr编译期。模板:偏特化仅类模板;SFINAE 实现编译期分发;CRTP 静态多态;可变参数+折叠表达式。 C++20 concepts 取代 SFINAE,约束清晰、报错友好。
高频真题:
std::move真的移动了吗?(没,只是转右值引用,移动在构造/赋值里发生) 为什么移动构造要 noexcept?(vector 扩容怕异常退化为拷贝)通用引用和右值引用区别?( T&&在推导上下文是通用引用,可绑左右值)lambda 捕获 [=]和[&]区别?this 捕获坑?(引用捕获活过局部变量会悬空)什么是 SFINAE?现在用什么替代?(模板替换失败非错;C++20 concepts) CRTP 有什么用?(编译期多态,无虚表开销) auto推导规则? std::vector<bool>的坑?(代理引用)const与 constexpr区别?(运行期只读 vs 编译期常量)写一个可变参数求和(折叠表达式或递归展开)。 怎么判断一个类型是否可调用/有某成员?(concepts requires 表达式 / SFINAE decltype探测)
小结
现代 C++ 的内核是零成本抽象 + 资源管理自动化 + 强类型约束。右值引用与 move 让值传递不再昂贵,模板与 concepts 把"编译期计算与约束"变成日常工具,lambda 与 auto 大幅减少样板。高级工程师的标志,是不仅能用这些特性,还能讲清它们背后的类型系统与编译期机制——这正是本篇的考察重心。
16. 补充:std::optional / variant / any(C++17 三剑客)
C++17 引入三个"现代替代裸指针/union/void*"的类型,显著提升安全性与表达力:
std::optional<T>:表示一个"可能有值也可能无值"的对象,替代"用空指针/特殊值表示不存在"。无堆分配,值语义,避免 nullptr解引用。
#include<optional>std::optional<int> find(int key){if (key == 0) return std::nullopt; // 无值return key * 2;}if (auto v = find(3); v) std::cout << *v;
std::variant<Ts...>:类型安全的联合体(tagged union),替代 C 风格union(C 的 union 不跟踪当前类型,易误用)。配合std::visit做编译期分派(见 STL 篇std::variant替代虚函数示例)。std::any:可存任意可拷贝类型的单值容器,类型擦除;需std::any_cast取出(类型不符抛异常)。适合插件/脚本桥接等"运行时才知类型"场景,但比variant慢且有堆分配。
面试点:能用 optional 表达"失败"比用异常更轻、比特殊值更清晰;variant+visit 是"用值语义 + 编译期多态替代继承"的现代手法。
17. 补充:std::string_view 与零拷贝字符串处理
std::string_view(C++17)是对已有字符串的只读视图(指针 + 长度),不拥有内存、不拷贝。用于函数参数避免不必要的 string 拷贝(尤其是子串切分)。
#include<string_view>voidlog(std::string_view s){ /* 只读,不拷贝 */ } // 接受 string / char* / 子串std::string big = "hello world";log(big); // 无拷贝log(std::string_view(big).substr(0,5)); // 切分也无拷贝
陷阱:string_view 不拥有底层内存,若指向的 string 被销毁/修改,string_view 悬空——绝不要返回指向局部 string 的 string_view,也不要长期持有。面试能指出"视图的生命周期必须短于被视图者"是安全关键。
18. 补充:结构化绑定与 if constexpr
结构化绑定(C++17):一行解包 tuple/pair/结构体成员,写代码更清爽(map 遍历 for (auto& [k,v] : m) 就是它)。
if constexpr(C++17):编译期条件分支,满足条件的分支参与实例化、不满足的直接丢弃(不要求语法合法)。这是模板里取代大量 SFINAE 的利器。
template <typename T>autoprocess(T v){ifconstexpr(std::is_pointer_v<T>)return *v; // T 是指针才实例化这行elsereturn v; // T 不是指针才实例化这行}
对比传统 SFINAE,if constexpr 阅读与维护成本大幅降低,是 C++17 模板编程的转折点。面试能同时展示 SFINAE(老)与 if constexpr/concepts(新)并说明取舍,体现经验厚度。
19. 真题演练(现代 C++)
Q1:std::move 一个 const 对象会发生什么? A:std::move(const T) 得到 const T&&(右值引用但带 const),会匹配拷贝构造而非移动构造(因为移动构造通常接受 T&& 非 const)。所以"移动 const 对象"实际是拷贝——这是常见反模式,说明不该把 const 对象当资源源。
Q2:auto 推导 std::vector<bool>::reference 的坑? A:vector<bool> 特化为位压缩,operator[] 返回的是代理类型(不是 bool&)。auto x = v[0]; 拿到的是代理对象,后续修改 v[0] 不会反映到 x;应写 bool x = v[0]; 或 auto&& x = v[0];。这是 auto + 代理引用最著名的坑。
Q3:模板特化与重载哪个优先级高? A:非模板函数 > 特化模板 > 主模板(在同样匹配时);重载决议先看参数匹配度,模板特化不参与重载决议而只参与"选了哪个模板后再选哪个特化"。要精确控制分发,常把特化写成重载或用 if constexpr/concepts 代替,避免"特化不参与重载"的困惑。
Q4:constexpr 函数能在运行时调用吗? A:能。constexpr 函数既可在编译期(参数都是常量时)求值,也可在运行期当普通函数用——它不强制编译期。要强制编译期求值用 consteval(C++20);要运行时也可但允许编译期用 constexpr。constinit(C++20)保证变量是编译期初始化、运行期不变。
Q5:为什么建议所有重写都加 override? A:编译器会校验基类确有该虚函数且签名匹配;若手误写成 virtual void sound(int)(参数错了)没加 override,会被当成新函数而非重写,多态悄然失效且毫无报错——这是隐蔽 bug。override 把这类错误变成编译错误。
20. 深度专题:模板编译模型与代码膨胀
模板是"按需实例化":只用到的具体类型才会生成代码。这带来零成本抽象(只为实际使用的类型付费),但也可能导致代码膨胀(code bloat)——同样的算法为 int、long、double… 各生成一份。
- 缓解膨胀
:把与类型无关的逻辑抽到非模板基类/自由函数(“非类型相关部分去模板化”),用 shared_impl公共实现 + 薄模板壳。 - 显式实例化
: template class Vector<int>;把实例化限制在一个 TU,避免每个编译单元都生成(配合extern template声明抑制重复实例化,加快编译)。 - 头文件代价
:模板多在头文件,改动触发大面积重编;用 PIML/隐式接口、模块化(C++20 modules)缓解。
// 头文件抑制实例化extern template classstd::vector<int>; // 别在当前 TU 实例化// 某 .cpp 里:template classstd::vector<int>; // 集中实例化一次
面试点:能权衡"泛型灵活 vs 编译时间/二进制体积",并知道 extern template、模块化等手段,是大型项目经验。
21. 真题再加码(现代 C++ 终极)
Q1:auto 和 decltype 在 decltype(auto) 里怎么配合? A:decltype(auto) 让返回类型精确沿用表达式的 decltype 类别(保留引用/值类别)。典型用于完美转发返回:decltype(auto) f() { return expr; } 既可能返回引用也可能返回值,由 expr 决定。对比 auto 会脱掉引用导致意外拷贝。面试能讲清"想保留引用时用 decltype(auto)"。
Q2:C++20 三件套(concepts / ranges / modules)各自解决什么? A:concepts 给模板加可读约束、报错友好(替代 SFINAE);ranges 提供惰性、可组合、管道式的算法(如 views::filter | views::transform),表达力强且无中间容器;modules 替代 #include 文本包含,消除宏污染、加速编译、改善封装。import std; 取代 #include <vector>。面试能列出三者价值体现对现代标准的掌握。
Q3:std::forward 和 std::move 能互相替代吗? A:不能。move 无条件转右值(丢弃值类别信息);forward<T> 按 T 推导结果条件地转左/右值(保持原类别)。转发万能引用参数必须用 forward,否则右值被当成左值导致意外拷贝;而 move 用于"我明确要搬走这个对象"。混用会破坏完美转发语义。
Q4:lambda 可以递归吗?怎么写? A:可以。[&] auto self = [&](int n){ return n<=1?1:n*self(n-1); };(C++14 泛型 lambda + 按引用捕获自身)或 std::function 包装。注意按值捕获自身会拷贝,需 mutable 或用 std::ref。面试能写出"按引用捕获自身实现递归 lambda"是细节。
Q5:constexpr 函数里能用 new/delete 吗? A:C++20 起 constexpr 允许动态分配(且必须在编译期释放,不能泄漏到运行期);C++17 及之前不允许。所以能写编译期计算的动态结构(如编译期可变长容器)。面试能指出"版本差异"体现对标准的时效掌握。
Q6:为什么说"现代 C++ 尽量不用裸 new"? A:裸 new 容易漏 delete、异常路径难释放。现代做法:用 std::make_unique/make_shared(异常安全、一次分配)、用容器(vector/string)管理连续内存、用 std::pmr 做池化、用 std::string_view 避免拷贝。裸 new 只留给底层库/分配器作者。面试能升华到"RAII 优先、所有权清晰"是工程成熟标志。
22. 高频速答 20 题(现代 C++ 闪电战)
- 右值引用作用?
支撑 move 语义,转移资源而非拷贝。 std::move真移动了吗?没,仅转右值引用,移动发生在构造/赋值。 std::forward用途?完美转发,保持参数原值类别。 - 通用引用 vs 右值引用?
T&&在推导上下文是通用引用,可绑左右值。 - 为何移动构造标
noexcept?防 vector 扩容遇异常退化为拷贝。 - 移动后对象状态?
valid but unspecified,可析构/赋值。 - 值类别三类?
lvalue / xvalue / prvalue。 auto推导规则?脱引用/顶层 const;引用需 auto&。decltypevsdecltype(auto)?前者保留类别;后者精确沿用表达式类别。 - lambda 本质?
匿名闭包类,含 operator()。 [=]vs[&]?值捕获拷贝、引用捕获绑定。 [this]坑?lambda 活得比对象久会悬空。 nullptr优于NULL?类型安全( std::nullptr_t),无重载歧义。constvsconstexpr?运行期只读 vs 编译期常量。 - SFINAE 是什么?
模板替换失败非错,用于编译期分发。 - C++20 替代 SFINAE?
concepts,约束清晰报错友好。 - CRTP 作用?
编译期多态,无虚表开销。 - 可变参数模板核心?
递归展开或 ...折叠(C++17)。 optional用途?表达"可能有值",替代空指针/特殊值。 string_view注意?不拥有内存,生命周期必须短于被视图者。
23. 临场速记清单(现代 C++ 篇面试前过一遍)
右值引用 T&&支撑 move;std::move仅转换不移动,移动发生在构造/赋值。std::forward完美转发,配合通用引用 T&&(推导上下文)保持值类别。move 操作标 noexcept;移动后源对象"valid but unspecified",可析构/赋值。值类别:lvalue / xvalue / prvalue; glvalue、rvalue为其并集。auto脱引用/顶层 const; decltype保留类别;decltype(auto)精确沿用。lambda = 闭包类; [=]值、[&]引用;[this]注意悬空;递归用按引用捕获自身。nullptr类型安全; enum class强类型;constexpr编译期常量(C++20 可动态分配)。模板:偏特化仅类模板;SFINAE 实现编译期分发;C++20 concepts 替代 SFINAE。 CRTP 静态多态(无虚表开销);可变参数模板 + 折叠表达式处理任意参数。 optional/ variant/string_view是现代替代裸指针/union/拷贝的利器。if constexpr编译期分支、丢弃不满足分支,取代大量 SFINAE。 现代 C++ 铁律:尽量不用裸 new,用 make_unique/容器/RAII,所有权清晰。
24. 补充:现代 C++ 易错 Top 6(避坑必看)
- 移动后继续用对象值
: std::move后对象处于 valid but unspecified 状态,再用其值(除非重新赋值)是隐患。 - 通用引用没用
forward: T&&参数不std::forward转发出去,右值会被当左值导致意外拷贝。 auto接vector<bool>::reference:拿到的是代理引用而非 bool&,悬空/失效导致诡异 bug。- lambda
[this]捕获后对象先析构:lambda 生命周期长于对象时 this悬空,应捕获成员副本或weak_ptr守卫。 - 模板特化以为参与重载决议
:特化只在"已选定主模板后"生效,不进入重载排序,分发错误应改用重载/ if constexpr/concepts。 const对象被move: std::move(const T)匹配拷贝而非移动,资源没搬走,是隐蔽的反模式。
25. 一句话口诀(现代 C++ 篇)
右值引用撑 move, move只转不搬。forward保类别,universal reference 配它。 move 标 noexcept,移动后别用值。auto脱 const, decltype留类别。lambda 是闭包, [=]值[&]引用。nullptr类型安全, enum class强类型。constexpr编译期,concepts 替 SFINAE。 CRTP 静态多态,无虚表开销。 optional/ variant/string_view现代三件宝。现代 C++ 少用裸 new,RAII 管所有权。
26. 补充:边界追问三连
- Q:C++20 协程的三个关键字?
A: co_await(挂起等待)、co_yield(产出值并挂起)、co_return(协程返回);底层靠promise_type/awaitable协作,是标准库/框架(如 cppcoro)构建异步的基础。 - Q:modules 相比
#include好处?A:模块化编译消除文本包含带来的宏污染、重复解析与"包含爆炸",显著加速编译、改善封装与接口边界。 import std;取代一堆#include。 - Q:什么时候不该用模板?
A:当类型集合固定且较少、可读性/编译速度更优先时,虚函数 + 接口更直观;模板适合"需要零成本抽象、类型参数化且类型无限"的场景,否则编译慢、报错难。
27. 收尾两点提醒
- 不要为了现代而现代
: move/shared_ptr/模板不是越多越好。能unique_ptr就别shared_ptr,能编译期if constexpr就别运行时分支,能值语义就别裸指针。可读性、可维护性优先,性能瓶颈处再用"重型武器"。 - 理解而非背诵
:面试官深挖时(“为什么移动构造要 noexcept”“concepts 怎么替代 SFINAE”“虚函数为什么阻止内联”),考察的是你能否讲清底层机制。把本篇的机制串成"类型系统 + 编译期 + 零成本抽象"一条主线,比记结论更有说服力。
夜雨聆风