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|五法则


“拷贝是复制,移动是偷走。

右值引用让我们合法地偷。”



📦 定义:

类型特征示例
左值 (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; // ❌ 错误:右值不能绑定左值引用

🧠 口诀:

“左值有名字能取址,右值临时用完即弃。”


C++11 新增语法:

T&&

用于捕获可移动的临时对象。

📦 示例 2:右值引用绑定

std::string s1 = "Hello";
std::string&& temp = std::string("World");
temp += "!"; // ✅ 右值引用允许修改临时对象

🧩 讲解:

  • && 右值引用绑定临时对象。
  • 可直接操作内部资源,无需复制。

移动语义的核心:“偷资源,不复制资源”

📦 示例 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:实现移动赋值

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:vector 中的性能差异

std::vector<std::string> v;
v.push_back("first");              // 调用拷贝构造
v.push_back(std::string("temp"));  // ✅ 调用移动构造

📤 输出(简化)

Copied!
Moved!

🧩 效率分析:

操作行为代价
拷贝构造分配新内存 + 复制数据O(n)
移动构造转移指针所有权O(1)

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:模板函数转发

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> 可完美保留参数的左/右值属性。

📦 示例 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 避免昂贵拷贝。
  • 可实现高效容器交换、临时缓存转移。

如果类定义了以下任意函数之一,应定义所有相关函数。

函数作用
拷贝构造复制对象
拷贝赋值拷贝赋值
析构函数释放资源
移动构造“偷取”资源
移动赋值转移所有权

📦 现代 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 风格?