S SmartDocs
Chuỗi bài: C++ cpp 342 dòng · Cập nhật 2026-04-03

move_semantics.cpp

C++/Part4_現代CPP/Ch17_移動語意/move_semantics.cpp

// ============================================================
// Ch17 — 移動語意(Move Semantics)
// 編譯:g++ -std=c++17 -Wall -o move_semantics move_semantics.cpp
// ============================================================

#include <iostream>
#include <string>
#include <vector>
#include <utility>
#include <chrono>
#include <cstring>
#include <algorithm>

// ── 輔助函式 ──
void section(const std::string& title) {
    std::cout << "\n========================================\n";
    std::cout << "  " << title << "\n";
    std::cout << "========================================\n";
}

// ============================================================
// 自管理字串類別 — 完整示範 Rule of Five
// ============================================================
class MyString {
public:
    // 預設建構子
    MyString() : data_(nullptr), size_(0) {
        std::cout << "    [預設建構] MyString()\n";
    }

    // 參數化建構子
    explicit MyString(const char* str) {
        size_ = std::strlen(str);
        data_ = new char[size_ + 1];
        std::strcpy(data_, str);
        std::cout << "    [建構] MyString(\"" << data_ << "\")\n";
    }

    // ── 1. 解構子 ──
    ~MyString() {
        std::cout << "    [解構] ~MyString("
                  << (data_ ? data_ : "null") << ")\n";
        delete[] data_;
    }

    // ── 2. 複製建構子 ──
    MyString(const MyString& other) : size_(other.size_) {
        data_ = new char[size_ + 1];
        std::strcpy(data_, other.data_);
        std::cout << "    [複製建構] MyString(\"" << data_ << "\")\n";
    }

    // ── 3. 複製賦值運算子 ──
    MyString& operator=(const MyString& other) {
        std::cout << "    [複製賦值] operator=(\"" << other.data_ << "\")\n";
        if (this != &other) {
            delete[] data_;
            size_ = other.size_;
            data_ = new char[size_ + 1];
            std::strcpy(data_, other.data_);
        }
        return *this;
    }

    // ── 4. 移動建構子 ──
    MyString(MyString&& other) noexcept
        : data_(other.data_), size_(other.size_) {
        std::cout << "    [移動建構] MyString(\"" << data_ << "\") ← 偷走資源\n";
        other.data_ = nullptr;
        other.size_ = 0;
    }

    // ── 5. 移動賦值運算子 ──
    MyString& operator=(MyString&& other) noexcept {
        std::cout << "    [移動賦值] operator=(\""
                  << (other.data_ ? other.data_ : "null") << "\")\n";
        if (this != &other) {
            delete[] data_;
            data_ = other.data_;
            size_ = other.size_;
            other.data_ = nullptr;
            other.size_ = 0;
        }
        return *this;
    }

    // 存取
    const char* c_str() const { return data_ ? data_ : ""; }
    size_t size() const { return size_; }
    bool empty() const { return size_ == 0; }

    friend std::ostream& operator<<(std::ostream& os, const MyString& s) {
        return os << s.c_str();
    }

private:
    char* data_;
    size_t size_;
};

// ── 回傳 MyString 的函式(展示 RVO)──
MyString createGreeting(const char* name) {
    MyString result("Hello, ");
    // 實際上編譯器可能直接用 NRVO 省略複製和移動
    return result;
}

int main() {
    std::cout << "===== Ch17:移動語意完整示範 =====\n";

    // ──────────────────────────────────────────
    // 1. 左值 vs 右值
    // ──────────────────────────────────────────
    section("1. 左值 vs 右值");
    {
        int x = 42;           // x 是左值,42 是右值
        int y = x + 1;        // x + 1 是右值,y 是左值

        std::cout << "x = " << x << " (左值,可取位址: " << &x << ")\n";
        std::cout << "y = " << y << " (左值)\n";

        // 字串的左值與右值
        std::string s1 = "Hello";        // "Hello" → 臨時 string → s1
        std::string s2 = s1;             // s1 是左值 → 複製
        std::string s3 = std::move(s1);  // std::move(s1) 轉為右值 → 移動

        std::cout << "s1(移動後)= \"" << s1 << "\"\n";
        std::cout << "s2(複製)= \"" << s2 << "\"\n";
        std::cout << "s3(移動)= \"" << s3 << "\"\n";
    }

    // ──────────────────────────────────────────
    // 2. 右值參考
    // ──────────────────────────────────────────
    section("2. 右值參考 (&&)");
    {
        // 左值參考
        int a = 10;
        int& lref = a;           // 左值參考綁定左值
        // int& lref2 = 42;      // 錯誤!左值參考不能綁定右值

        const int& clref = 42;   // const 左值參考可以綁定右值

        // 右值參考
        int&& rref = 42;         // 右值參考綁定右值
        // int&& rref2 = a;      // 錯誤!右值參考不能綁定左值
        int&& rref3 = std::move(a);  // std::move 將左值轉為右值

        std::cout << "lref = " << lref << "\n";
        std::cout << "clref = " << clref << "\n";
        std::cout << "rref = " << rref << "\n";
        std::cout << "rref3 = " << rref3 << "\n";

        // 重要觀念:rref 本身是一個左值!(它有名字、有位址)
        // int&& rref4 = rref;  // 錯誤!rref 是左值
        int&& rref4 = std::move(rref);  // 需要 std::move
        std::cout << "rref4 = " << rref4
                  << "(rref 本身是左值,需要 std::move)\n";
    }

    // ──────────────────────────────────────────
    // 3. MyString 的複製 vs 移動
    // ──────────────────────────────────────────
    section("3. MyString 複製 vs 移動");
    {
        std::cout << "--- 建構 ---\n";
        MyString s1("C++");

        std::cout << "\n--- 複製建構 ---\n";
        MyString s2 = s1;  // 觸發複製建構子
        std::cout << "s1 = \"" << s1 << "\"\n";
        std::cout << "s2 = \"" << s2 << "\"\n";

        std::cout << "\n--- 移動建構 ---\n";
        MyString s3 = std::move(s1);  // 觸發移動建構子
        std::cout << "s1(移動後)= \"" << s1 << "\" (empty: "
                  << (s1.empty() ? "是" : "否") << ")\n";
        std::cout << "s3 = \"" << s3 << "\"\n";

        std::cout << "\n--- 複製賦值 ---\n";
        MyString s4("Old");
        s4 = s2;  // 觸發複製賦值
        std::cout << "s4 = \"" << s4 << "\"\n";

        std::cout << "\n--- 移動賦值 ---\n";
        MyString s5("Temp");
        s5 = std::move(s3);  // 觸發移動賦值
        std::cout << "s3(移動後)= \"" << s3 << "\"\n";
        std::cout << "s5 = \"" << s5 << "\"\n";

        std::cout << "\n--- 離開作用域,所有物件解構 ---\n";
    }

    // ──────────────────────────────────────────
    // 4. std::move 的本質
    // ──────────────────────────────────────────
    section("4. std::move 的本質");
    {
        std::cout << "std::move 實際上等同於:\n";
        std::cout << R"(
  template<typename T>
  typename std::remove_reference<T>::type&& move(T&& t) {
      return static_cast<typename std::remove_reference<T>::type&&>(t);
  }
)";
        std::cout << "→ 它只是一個 static_cast,不做任何實際移動\n";
        std::cout << "→ 真正的移動發生在移動建構子或移動賦值中\n";

        // 示範:std::move 對 const 物件無效
        const std::string cs = "不可移動";
        std::string t = std::move(cs);  // 實際上呼叫複製建構子!
        std::cout << "\nconst 物件 move 後:\n";
        std::cout << "  原始值仍為:\"" << cs << "\"\n";
        std::cout << "  新值為:\"" << t << "\"(是複製,不是移動)\n";
    }

    // ──────────────────────────────────────────
    // 5. 移動語意的效能優勢
    // ──────────────────────────────────────────
    section("5. 效能比較:複製 vs 移動");
    {
        const int N = 100000;

        // 建立一個大向量
        std::vector<std::string> source;
        source.reserve(N);
        for (int i = 0; i < N; i++) {
            source.push_back("This is a somewhat long string for testing #"
                             + std::to_string(i));
        }

        // 複製
        auto t1 = std::chrono::high_resolution_clock::now();
        std::vector<std::string> copied = source;  // 複製所有字串
        auto t2 = std::chrono::high_resolution_clock::now();

        // 移動
        auto t3 = std::chrono::high_resolution_clock::now();
        std::vector<std::string> moved = std::move(source);  // 移動(偷走指標)
        auto t4 = std::chrono::high_resolution_clock::now();

        auto copy_us = std::chrono::duration_cast<std::chrono::microseconds>(t2 - t1).count();
        auto move_us = std::chrono::duration_cast<std::chrono::microseconds>(t4 - t3).count();

        std::cout << "複製 " << N << " 個字串:" << copy_us << " μs\n";
        std::cout << "移動 " << N << " 個字串:" << move_us << " μs\n";
        std::cout << "移動快了約 " << (copy_us > 0 ? copy_us / std::max(move_us, (long long)1) : 0)
                  << " 倍\n";
        std::cout << "source 移動後大小:" << source.size() << "\n";
    }

    // ──────────────────────────────────────────
    // 6. Rule of Five 完整範例
    // ──────────────────────────────────────────
    section("6. Rule of Five 完整範例");
    {
        std::cout << "MyString 類別實作了全部五個特殊成員函式:\n";
        std::cout << "  1. 解構子         ~MyString()\n";
        std::cout << "  2. 複製建構子     MyString(const MyString&)\n";
        std::cout << "  3. 複製賦值運算子 operator=(const MyString&)\n";
        std::cout << "  4. 移動建構子     MyString(MyString&&) noexcept\n";
        std::cout << "  5. 移動賦值運算子 operator=(MyString&&) noexcept\n\n";

        std::cout << "--- 展示所有操作 ---\n";
        MyString a("Alpha");
        MyString b(a);               // 複製建構
        MyString c(std::move(a));    // 移動建構
        MyString d("Beta");
        d = b;                       // 複製賦值
        MyString e("Gamma");
        e = std::move(d);            // 移動賦值
        std::cout << "\n--- 結束,解構所有物件 ---\n";
    }

    // ──────────────────────────────────────────
    // 7. 移動語意與容器
    // ──────────────────────────────────────────
    section("7. 移動語意與容器");
    {
        std::vector<MyString> vec;
        std::cout << "--- push_back 臨時物件(移動) ---\n";
        vec.push_back(MyString("Temp"));  // 臨時物件 → 移動

        std::cout << "\n--- push_back 具名物件(複製) ---\n";
        MyString named("Named");
        vec.push_back(named);  // 具名物件 → 複製

        std::cout << "\n--- push_back + std::move(移動) ---\n";
        MyString movable("Movable");
        vec.push_back(std::move(movable));  // 強制移動

        std::cout << "\nmovable 移動後:\"" << movable << "\"\n";

        std::cout << "\n容器內容:\n";
        for (size_t i = 0; i < vec.size(); i++) {
            std::cout << "  [" << i << "] = \"" << vec[i] << "\"\n";
        }

        std::cout << "\n--- 容器銷毀 ---\n";
    }

    // ──────────────────────────────────────────
    // 8. RVO / NRVO
    // ──────────────────────────────────────────
    section("8. RVO / NRVO");
    {
        std::cout << "呼叫 createGreeting:\n";
        MyString greeting = createGreeting("World");
        std::cout << "greeting = \"" << greeting << "\"\n";
        std::cout << "(觀察:可能只有一次建構,沒有複製或移動 → NRVO 生效)\n\n";

        std::cout << "重要提醒:\n";
        std::cout << "  ✓ return local_var;              — 允許 NRVO\n";
        std::cout << "  ✗ return std::move(local_var);   — 阻止 NRVO!\n";
    }

    // ──────────────────────────────────────────
    // 9. 被移動後的物件狀態
    // ──────────────────────────────────────────
    section("9. 被移動後的物件");
    {
        std::string s = "Important Data";
        std::string t = std::move(s);

        std::cout << "移動後的 s:\"" << s << "\"\n";
        std::cout << "s.size():" << s.size() << "\n";
        std::cout << "s.empty():" << (s.empty() ? "是" : "否") << "\n";

        // 安全操作:重新賦值
        s = "重新賦值是安全的";
        std::cout << "重新賦值後:\"" << s << "\"\n";

        std::cout << "\n移動後可以做的事:\n";
        std::cout << "  ✓ 銷毀(解構子)\n";
        std::cout << "  ✓ 重新賦值\n";
        std::cout << "  ✓ 呼叫不依賴前置條件的操作(如 size(), empty())\n";
        std::cout << "  ✗ 假設有特定內容(未定義的狀態)\n";
    }

    std::cout << "\n===== 移動語意示範結束 =====\n";
    return 0;
}

Bài viết liên quan