Chapter 18:Visiting with the New C++ Standard
对齐第6版 Chapter 18:掌握 uniform initialization、auto、smart pointer、scoped enum、rvalue reference/move、lambda、wrapper 与 variadic template。
学习目标
- 能比较 uniform initialization、
auto/decltype与旧声明形式,复现 narrowing 和 initializer_list 选择差异 - 能实现 unique owner 类型的 rvalue reference、move constructor/assignment,并判断 smart pointer 与 scoped enum 的契约收益
- 能设计 lambda、
std::function/bindwrapper 和 variadic template,分析 capture、type erasure 与 parameter pack 展开成本
为什么新标准特性要按契约而不是年份学习
第六版把 C++11 作为“新标准”介绍。真正需要保留的不是发布年份,而是它把哪些错误交给类型系统:brace 可诊断 narrowing,smart pointer 把 owner 写进类型,override 检查虚函数,move 区分复制和资源转移,lambda 把局部策略变成 callable object。
uniform initialization 统一语法但仍有重载规则
统一初始化(uniform initialization)用 braces 初始化标量、聚合、类和容器,并禁止许多窄化转换。空 braces 值初始化。它减少语法分裂,却不会让所有构造候选等价:存在 initializer_list constructor 时,列表初始化会优先尝试它。
int count{42};
std::vector<int> values{1, 2, 3};
// int narrowed{3.5}; // error: narrowing
std::vector<int> sized(3, 7); // {7,7,7}
std::vector<int> listed{3, 7}; // {3,7}() 与 {} 在 vector 示例中语义不同。审计初始化时写出候选 constructor、是否走 initializer_list、元素数量和窄化规则,不能只以“现代风格”替换符号。
auto 与 decltype 分别从初始化器和表达式推导
自动类型推导(auto)从 initializer 推导变量类型,常去掉顶层 const/reference;需要引用时显式写 auto&、const auto& 或 auto&&。decltype(expr) 按表达式规则保留更多类型和值类别信息,额外括号会影响结果。
const int answer = 42;
auto copy = answer; // int
const auto& view = answer; // const int&
decltype(answer) exact = answer; // const int
decltype((answer)) ref = answer; // const int&auto 消除冗长拼写,不消除理解类型的责任。尤其 range-for 应判断复制还是引用,iterator/value 类型决定生命周期和修改语义。
replaces · mixed (), =, aggregate forms
contract · brace syntax + narrowing diagnosticsproof · initializer_list preference audited
replaces · repeated dependent type spelling
contract · deduction follows precise rulesproof · reference/cv preservation checked
replaces · unscoped names + implicit int
contract · qualified names + no implicit integerproof · underlying representation deliberate
replaces · implicit special-member guesses
contract · class intent compiler-checkedproof · copy/move/virtual surface explicit
smart pointers 与 scoped enumeration 收紧隐式边
C++11 的 unique_ptr、shared_ptr、weak_ptr 用模板表达 owner;make_shared/make_unique(后者在 C++14 标准化)减少裸 new 暴露。作用域枚举(scoped enumeration)用 enum class 把枚举项限制在类型 scope,并禁止自动转整数。
enum class Status : std::uint8_t { idle, running, failed };
Status state = Status::running;
// int raw = state; // explicit conversion required
auto task = std::make_unique<Task>();
run(*task); // borrow is visible at call sitescoped enum 避免全局枚举名冲突和意外算术;底层类型可控制存储/协议,但跨文件格式仍需显式编码版本。
rvalue reference 区分可借用资源的表达式
右值引用(rvalue reference, T&&)可绑定临时值或 xvalue,使 overload 能选择 move operation。命名的右值引用变量在表达式中仍是 lvalue;要继续转移需显式 std::move 或泛型代码中的 std::forward。
class Buffer {
public:
explicit Buffer(std::size_t size)
: data_{new int[size]}, size_{size} {}
~Buffer() { delete[] data_; }
Buffer(Buffer&& other) noexcept
: data_{other.data_}, size_{other.size_} {
other.data_ = nullptr;
other.size_ = 0;
}
private:
int* data_{nullptr};
std::size_t size_{0};
};move constructor 接管资源并把 source 留在 valid but unspecified(这里明确为空)状态。noexcept 让许多容器在重分配时愿意 move 而非 copy。
move semantics 是类型协议,不是强制偷取
移动语义(move semantics)由 move constructor/assignment 的实现定义。std::move(x) 本身不移动字节,只把 x 转成 xvalue 以参与 overload resolution;若类型无 move,仍可能复制。
Buffer& Buffer::operator=(Buffer&& other) noexcept {
if (this == &other) return *this;
delete[] data_;
data_ = other.data_;
size_ = other.size_;
other.data_ = nullptr;
other.size_ = 0;
return *this;
}定义 destructor/copy/move 中任一项会影响其余项是否隐式生成,应按 Rule of Five/Zero 审查。优先让 vector/unique_ptr 成员提供正确 move,避免手写裸 owner。
bufferselect · copy overload by defaultsource · unchangedtarget · independent resourcestd::move(buffer)select · rvalue-reference overloadsource · valid but unspecifiedtarget · resource transferredBuffer{size}select · move or elisionsource · temporary lifetime endstarget · constructed efficientlybuffer = Buffer{}select · assign known statesource · valid known emptytarget · old owner releases/replacesnew class features 让设计意图可编译检查
新类特性(new class features)包括 =default、=delete、delegating constructors、inheriting constructors、override、final、类内成员初始化与 explicit conversion operator。它们减少重复并把禁止复制、覆盖签名和默认状态写进声明。
class Session final {
public:
Session() : Session{Options{}} {}
explicit Session(Options options) : options_{std::move(options)} {}
Session(const Session&) = delete;
Session& operator=(const Session&) = delete;
private:
Options options_{};
};delegating constructor 最终应汇聚到一个建立不变量的目标构造;delete 比把复制声明为 private 且不定义更早、更清楚地诊断。
lambda expression 生成带 capture 的闭包对象
lambda 由 capture、参数、可选 specifier/返回类型和 body 构成。按值 capture 复制创建时的值,按引用 capture 借用外部对象;闭包逃逸 scope 时引用可能悬空。默认 operator() 为 const,mutable 允许修改闭包内部副本。
int limit = 10;
auto below = [limit](int value) { return value < limit; };
std::vector<int> values{3, 14, 7};
auto count = std::count_if(values.begin(), values.end(), below);[&] 和 [=] 隐藏每个依赖方向,大型或异步 callable 更适合显式 capture。捕获 this 是捕获 pointer,不延长对象生命周期。
wrappers 统一 callable,代价是 type erasure
std::function<R(Args...)> 是 callable wrapper,可存函数、lambda、functor 或 bind result,提供统一运行时类型;代价可能包括间接调用、分配和 copyability 限制。std::bind 预绑定参数并以 placeholder 重排,lambda 往往更直观。
#include <functional>
std::function<bool(int)> predicate = [limit](int value) {
return value < limit;
};
auto bound = std::bind(std::less<int>{}, std::placeholders::_1, limit);若 callable 只在模板内部使用,保留具体 lambda 类型通常更轻;需要跨非模板接口、容器存储多种 callable 或运行时替换时,std::function 的 type erasure 才有价值。
variadic templates 对 parameter pack 展开
可变参数模板(variadic template)用 template parameter pack 接受任意数量和类型参数。C++11 常用递归展开:定义空/单项 base case,再每次处理一个参数;现代 fold expression 是后续标准的更简洁形式。
↡以类型/函数参数包接收可变数量参数,并在编译期展开生成具体函数或类的模板。void logLine(std::ostream& out) {
out << '\n';
}
template <typename T, typename... Rest>
void logLine(std::ostream& out, T&& first, Rest&&... rest) {
out << std::forward<T>(first);
if constexpr (sizeof...(rest) > 0) out << ' ';
logLine(out, std::forward<Rest>(rest)...);
}示例中的 if constexpr 属于 C++17;若严格使用 C++11,可通过 overload/base case 控制分隔符。关键契约是 pack 大小、展开位置、forwarding 和递归终止。
先预测 lambda、std::function、bind 和 variadic 四种 callable 的存储/调用成本与生命周期,再切换实验面板核对。
三步验证新特性是否改善契约
第一步:对照旧写法与编译器证据
为 brace/auto/scoped enum/default-delete-override 分别制造 narrowing、类型推导和签名错误,确认诊断改变。
replaces · mixed (), =, aggregate forms
contract · brace syntax + narrowing diagnosticsproof · initializer_list preference audited
replaces · repeated dependent type spelling
contract · deduction follows precise rulesproof · reference/cv preservation checked
replaces · unscoped names + implicit int
contract · qualified names + no implicit integerproof · underlying representation deliberate
replaces · implicit special-member guesses
contract · class intent compiler-checkedproof · copy/move/virtual surface explicit
小结
- uniform initialization 统一语法并诊断 narrowing,但 initializer_list 会改变 overload selection
- auto/decltype 遵循精确推导规则,仍需显式决定 reference 与 const
- smart pointers and scoped enumerations 分别收紧 owner 与名字/整数转换边
- rvalue references 让 overload 区分可移动表达式,move semantics 决定资源转移和 source 状态
- new class features 把 default/delete/override 等意图交给编译器检查
- lambda、wrappers and variadic templates 提供不同 callable 复用与成本模型
练习
- 问题 1:审计 brace 迁移。
vector<int> v(5,1)改成v{5,1}后为何测试失败?
- 问题 2:验证 move。
target = std::move(source)后能否断言 source.empty()?
- 问题 3:选择 callable 表示。 一个算法只在模板函数内立即调用 predicate,是否需要 std::function?
名词解释
名词解释
本章出现的专业名词,用大白话再讲一遍。
- 统一初始化
- 以 braces 初始化并诊断窄化的统一语法。
- 自动类型推导
- 由初始化器/表达式规则确定声明类型的机制。
- 智能指针
- 通过类型语义表达动态对象 owner 的 RAII 模板。
- 作用域枚举
- 枚举项需限定且不隐式转整数的 enum class。
- 右值引用
- 绑定临时/xvalue 并支持移动 overload 的 T&& 引用。
- 移动语义
- 从可移动源接管资源并保持源有效的特殊成员协议。
- lambda
- 以 capture 和函数体生成闭包对象的表达式。
- 包装器
- 用统一签名保存/调用不同 callable 的对象。
- 可变参数模板
- 通过 parameter pack 接收并展开可变数量参数的模板。