cs106l-lecture-5-4-move-semantics
Course: 斯坦福大学: C++标准库编程 | C++ Standard Library Programming
🎮 CS106L Lecture 5.4 — Move Semantics
🏛 课程:Stanford CS106L — Standard C++ Programming Language
📍 主题:移动语义与右值引用(Move Semantics & Rvalue References)
🎯 目标:理解 C++11 引入移动语义的动机与机制,掌握右值引用、std::move、完美转发与 Rule of Five 的关系。
🧭 课程定位
在上一讲《Special Member Functions》中,我们学习了“三法则 / 五法则”与对象生命周期管理。
本讲进一步解释:
💡 为什么“移动(move)”比“拷贝(copy)”更高效?
💡 为什么右值引用能让 C++ 既安全又高性能?
这节课揭开现代 C++ 性能魔法的核心。
🏷 关键词速记
Rvalue|右值
Lvalue|左值
Rvalue Reference|右值引用
Move Constructor|移动构造函数
Move Assignment|移动赋值
std::move|类型转换辅助
Perfect Forwarding|完美转发
Rule of Five|五法则
🎮 30 秒课程摘要
“拷贝是复制,移动是偷走。
右值引用让我们合法地偷。”
📚 核心内容讲解
🧩 1️⃣ 左值(Lvalue)与右值(Rvalue)
📦 定义:
| 类型 | 特征 | 示例 |
|---|---|---|
| 左值 (Lvalue) | 在内存中有持久地址的对象 | int x = 5; → x 是左值 |
| 右值 (Rvalue) | 临时存在、不可取地址的对象 | 10, x + 1, std::string("Hi") |
📦 示例 1:
int x = 10;
int& ref1 = x; // ✅ 左值引用
int&& ref2 = 5; // ✅ 右值引用
// int& ref3 = 5; // ❌ 错误:右值不能绑定左值引用
🧠 口诀:
“左值有名字能取址,右值临时用完即弃。”
🧩 2️⃣ 右值引用(Rvalue Reference)
C++11 新增语法:
T&&用于捕获可移动的临时对象。
📦 示例 2:右值引用绑定
std::string s1 = "Hello";
std::string&& temp = std::string("World");
temp += "!"; // ✅ 右值引用允许修改临时对象
🧩 讲解:
&&右值引用绑定临时对象。- 可直接操作内部资源,无需复制。
🧩 3️⃣ 移动构造函数(Move Constructor)
移动语义的核心:“偷资源,不复制资源”。
📦 示例 3:移动构造实现
class Buffer {
public:
Buffer(size_t size) : data(new int[size]), size(size) {}
~Buffer() { delete[] data; }
// 拷贝构造
Buffer(const Buffer& rhs)
: data(new int[rhs.size]), size(rhs.size) {
std::copy(rhs.data, rhs.data + size, data);
std::cout << "Copied!\n";
}
// 移动构造
Buffer(Buffer&& rhs) noexcept
: data(rhs.data), size(rhs.size) {
rhs.data = nullptr; // 夺取资源
rhs.size = 0;
std::cout << "Moved!\n";
}
private:
int* data;
size_t size;
};📦 使用示例
Buffer a(10);
Buffer b = std::move(a); // ✅ 调用移动构造
📤 输出:
Moved!
🧩 讲解:
std::move()只是类型转换(左值 → 右值)。- 移动构造后,原对象被置为“安全但空”。
🧩 4️⃣ 移动赋值运算符(Move Assignment Operator)
📦 示例 4:实现移动赋值
Buffer& operator=(Buffer&& rhs) noexcept {
if (this == &rhs) return *this;
delete[] data; // 释放旧资源
data = rhs.data; // 接管资源
size = rhs.size;
rhs.data = nullptr; // 置空
rhs.size = 0;
std::cout << "Move Assigned!\n";
return *this;
}🧠 讲解:
- 接收右值(临时对象) → 直接转移资源所有权
- 移动后源对象应保持析构安全
🧩 5️⃣ 移动 vs 拷贝 性能对比
📦 示例 5:vector 中的性能差异
std::vector<std::string> v;
v.push_back("first"); // 调用拷贝构造
v.push_back(std::string("temp")); // ✅ 调用移动构造
📤 输出(简化)
Copied!
Moved!
🧩 效率分析:
| 操作 | 行为 | 代价 |
|---|---|---|
| 拷贝构造 | 分配新内存 + 复制数据 | O(n) |
| 移动构造 | 转移指针所有权 | O(1) |
🧩 6️⃣ std::move() 的真实作用
std::move() 并不移动任何东西。
它只是一个类型转换函数:
template<typename T>
typename std::remove_reference<T>::type&& move(T&& arg) noexcept;📦 示例 6:
std::string a = "Hello";
std::string b = std::move(a);📤 输出:
Moved!
🧠 讲解:
- 将
a强制视为右值引用,触发移动构造。 a的资源被“偷走”,但仍是合法状态(为空字符串)。
🧩 7️⃣ 完美转发(Perfect Forwarding)
用于在模板中“保持参数值类别(左/右值)”。
📦 示例 7:模板函数转发
template<typename T>
void wrapper(T&& arg) {
process(std::forward<T>(arg)); // 保持原始左/右值语义
}📦 示例 8:std::forward 工作原理
void process(std::string& s) { std::cout << "Lvalue\n"; }
void process(std::string&& s) { std::cout << "Rvalue\n"; }
template<typename T>
void call(T&& arg) {
process(std::forward<T>(arg));
}
std::string x = "Test";
call(x); // 输出: Lvalue
call(std::string()); // 输出: Rvalue
🧠 解释:
T&&+std::forward<T>可完美保留参数的左/右值属性。
🧩 8️⃣ swap 的零拷贝实现
📦 示例 9:零拷贝 swap
template<typename T>
void mySwap(T& a, T& b) {
T temp = std::move(a);
a = std::move(b);
b = std::move(temp);
}🧠 讲解:
- 使用
std::move避免昂贵拷贝。 - 可实现高效容器交换、临时缓存转移。
🧩 9️⃣ Rule of Five 回顾
如果类定义了以下任意函数之一,应定义所有相关函数。
| 函数 | 作用 |
|---|---|
| 拷贝构造 | 复制对象 |
| 拷贝赋值 | 拷贝赋值 |
| 析构函数 | 释放资源 |
| 移动构造 | “偷取”资源 |
| 移动赋值 | 转移所有权 |
📦 现代 C++ 模板
class Resource {
public:
Resource(); ~Resource();
Resource(const Resource&);
Resource(Resource&&) noexcept;
Resource& operator=(const Resource&);
Resource& operator=(Resource&&) noexcept;
};🧠 口诀:
“能动就别拷,能省就别新。”
🧩 🔟 移动语义与智能指针
智能指针通过移动语义实现资源独占。
📦 示例 10:unique_ptr 移动语义
std::unique_ptr<int> p1 = std::make_unique<int>(5);
std::unique_ptr<int> p2 = std::move(p1); // ✅ 合法移动
// p1 无效,p2 拥有资源
📤 输出:
Moved!
🧩 讲解:
unique_ptr禁止拷贝,只能移动。- 保证资源独占性与生命周期安全。
🧠 记忆口诀
“左值常驻,右值速逝;
move 是假动作,forward 真传递;
拷贝复制,移动偷取。”
🧪 实践练习
✅ 练习 1:观察移动构造
创建一个打印拷贝/移动日志的类,测试 push_back() 与 emplace_back() 区别。
✅ 练习 2:自定义 swap
用 std::move 重写 swap() 并比较效率。
✅ 练习 3:完美转发
写模板函数 forwardTo(),验证左/右值推导行为。
✅ 练习 4:移动禁用类
创建禁止拷贝但允许移动的资源类(模仿 unique_ptr)。
🪄 工程与设计建议
- 若类管理资源(指针、句柄),请实现 Rule of Five。
- 优先使用移动构造代替拷贝。
- 所有可能触发临时对象的 API 参数应使用 值传递 + move 语义。
- 使用
emplace_back()而非push_back()以避免拷贝。 - 禁止盲目使用
std::move()—— 仅在对象不再使用时调用。 - 优先通过智能指针(
unique_ptr/shared_ptr)托管资源。
✅ 小结表
| 概念 | 含义 | 示例 |
|---|---|---|
| 左值引用 | 可绑定持久对象 | int& r = x; |
| 右值引用 | 可绑定临时对象 | int&& r = 5; |
| 移动构造 | 偷资源构造 | T(T&&) |
| 移动赋值 | 偷资源赋值 | operator=(T&&) |
std::move | 将左值转为右值 | std::move(x) |
std::forward | 保留值类别 | 模板转发参数 |
| RVO | 返回值优化 | 避免拷贝返回 |
“移动语义是现代 C++ 的灵魂——
它让抽象既安全,又高效。”
是否希望我继续整理下一讲
👉 Lecture 6 — Smart Pointers & Resource Management
(unique_ptr / shared_ptr / weak_ptr、引用计数、循环引用与 RAII 深化)为同样 Notion 风格?