S SmartDocs
Serie: C++ cpp 422 líneas · Actualizado 2026-04-03

perfect_forwarding.cpp

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

// ============================================================
// Ch17 — 完美轉發(Perfect Forwarding)
// 編譯:g++ -std=c++17 -Wall -o perfect_forwarding perfect_forwarding.cpp
// ============================================================

#include <iostream>
#include <string>
#include <memory>
#include <utility>
#include <vector>
#include <chrono>
#include <functional>

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

// ============================================================
// 1. 值類別偵測
// ============================================================

void process(int& x) {
    std::cout << "  process(int&) — 收到左值: " << x << "\n";
}

void process(int&& x) {
    std::cout << "  process(int&&) — 收到右值: " << x << "\n";
}

void process(const std::string& s) {
    std::cout << "  process(const string&) — 收到左值: \"" << s << "\"\n";
}

void process(std::string&& s) {
    std::cout << "  process(string&&) — 收到右值: \"" << s << "\"\n";
}

// ============================================================
// 2. 不完美的包裝函式
// ============================================================

// 版本 A:所有引數都變成左值(即使原本是右值)
template<typename T>
void bad_wrapper(T arg) {
    std::cout << "  bad_wrapper 呼叫 process:\n    ";
    process(arg);  // arg 永遠是左值!
}

// ============================================================
// 3. 完美轉發的包裝函式
// ============================================================

template<typename T>
void good_wrapper(T&& arg) {
    std::cout << "  good_wrapper 呼叫 process:\n    ";
    process(std::forward<T>(arg));  // 保留原始值類別
}

// ============================================================
// 4. 通用日誌包裝器
// ============================================================

template<typename Func, typename... Args>
decltype(auto) logAndCall(const std::string& funcName,
                          Func&& func,
                          Args&&... args) {
    std::cout << "  [LOG] 呼叫 " << funcName << "\n";
    auto start = std::chrono::high_resolution_clock::now();

    decltype(auto) result = std::forward<Func>(func)(
        std::forward<Args>(args)...
    );

    auto end = std::chrono::high_resolution_clock::now();
    auto elapsed = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
    std::cout << "  [LOG] " << funcName << " 完成,耗時 " << elapsed << " μs\n";

    return result;
}

// 為 void 回傳值提供特化版本
template<typename Func, typename... Args>
void logAndCallVoid(const std::string& funcName,
                    Func&& func,
                    Args&&... args) {
    std::cout << "  [LOG] 呼叫 " << funcName << "\n";
    std::forward<Func>(func)(std::forward<Args>(args)...);
    std::cout << "  [LOG] " << funcName << " 完成\n";
}

// ============================================================
// 5. 工廠函式模式
// ============================================================

class Widget {
public:
    Widget() : id_(0), name_("default") {
        std::cout << "    Widget() 預設建構\n";
    }
    Widget(int id, const std::string& name) : id_(id), name_(name) {
        std::cout << "    Widget(" << id_ << ", \"" << name_ << "\") 建構\n";
    }
    Widget(int id, std::string&& name) : id_(id), name_(std::move(name)) {
        std::cout << "    Widget(" << id_ << ", \"" << name_ << "\") 建構(name 被移動)\n";
    }

    void info() const {
        std::cout << "    Widget #" << id_ << ": " << name_ << "\n";
    }

    ~Widget() {
        std::cout << "    Widget(" << id_ << ") 解構\n";
    }

private:
    int id_;
    std::string name_;
};

// 自訂 make_unique — 使用完美轉發
template<typename T, typename... Args>
std::unique_ptr<T> my_make_unique(Args&&... args) {
    return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

// ============================================================
// 6. emplace_back vs push_back
// ============================================================

class HeavyObject {
public:
    HeavyObject(int id, const std::string& data)
        : id_(id), data_(data) {
        std::cout << "    HeavyObject(" << id_ << ") 建構\n";
    }
    HeavyObject(const HeavyObject& other)
        : id_(other.id_), data_(other.data_) {
        std::cout << "    HeavyObject(" << id_ << ") 複製建構\n";
    }
    HeavyObject(HeavyObject&& other) noexcept
        : id_(other.id_), data_(std::move(other.data_)) {
        std::cout << "    HeavyObject(" << id_ << ") 移動建構\n";
    }
    ~HeavyObject() {
        std::cout << "    HeavyObject(" << id_ << ") 解構\n";
    }

    int id() const { return id_; }

private:
    int id_;
    std::string data_;
};

// ============================================================
// 7. 引用摺疊規則示範
// ============================================================

template<typename T>
std::string describeType(T&&) {
    if constexpr (std::is_lvalue_reference_v<T>) {
        return "T 被推導為左值參考(T&)";
    } else {
        return "T 被推導為非參考型別(T)";
    }
}

// ============================================================
// 8. 條件式移動
// ============================================================

template<typename T>
void conditionalProcess(T&& arg, bool shouldMove) {
    if (shouldMove) {
        process(std::forward<T>(arg));
    } else {
        // 強制以左值處理
        auto& lref = arg;
        process(lref);
    }
}

int main() {
    std::cout << "===== Ch17:完美轉發示範 =====\n";

    // ──────────────────────────────────────────
    // 1. 轉發參考基礎
    // ──────────────────────────────────────────
    section("1. 轉發參考(T&&)");
    {
        int x = 42;
        std::cout << "傳入左值 x:\n";
        std::cout << "  " << describeType(x) << "\n";

        std::cout << "傳入右值 42:\n";
        std::cout << "  " << describeType(42) << "\n";

        std::cout << "傳入 std::move(x):\n";
        std::cout << "  " << describeType(std::move(x)) << "\n";

        std::cout << "\n引用摺疊規則:\n";
        std::cout << "  T& &   → T&  (左值參考的左值參考 → 左值參考)\n";
        std::cout << "  T& &&  → T&  (左值參考的右值參考 → 左值參考)\n";
        std::cout << "  T&& &  → T&  (右值參考的左值參考 → 左值參考)\n";
        std::cout << "  T&& && → T&& (右值參考的右值參考 → 右值參考)\n";
    }

    // ──────────────────────────────────────────
    // 2. 不完美轉發的問題
    // ──────────────────────────────────────────
    section("2. 不完美轉發 vs 完美轉發");
    {
        int x = 10;

        std::cout << "--- 直接呼叫 ---\n";
        std::cout << "process(x)(左值):\n  ";
        process(x);
        std::cout << "process(42)(右值):\n  ";
        process(42);

        std::cout << "\n--- 不完美的包裝(bad_wrapper) ---\n";
        std::cout << "bad_wrapper(x):\n";
        bad_wrapper(x);   // arg 是副本,永遠是左值
        std::cout << "bad_wrapper(42):\n";
        bad_wrapper(42);  // 右值變成了左值!

        std::cout << "\n--- 完美轉發(good_wrapper) ---\n";
        std::cout << "good_wrapper(x):\n";
        good_wrapper(x);   // 保持左值
        std::cout << "good_wrapper(42):\n";
        good_wrapper(42);  // 保持右值 ✓
    }

    // ──────────────────────────────────────────
    // 3. std::forward 如何運作
    // ──────────────────────────────────────────
    section("3. std::forward 的原理");
    {
        std::cout << R"(
  template<typename T>
  void wrapper(T&& arg) {
      // 當傳入左值 int x:
      //   T 推導為 int&
      //   arg 的型別是 int& && → int&(引用摺疊)
      //   forward<int&>(arg) → 回傳 int&(左值)
      //
      // 當傳入右值 42:
      //   T 推導為 int
      //   arg 的型別是 int&&
      //   forward<int>(arg) → 回傳 int&&(右值)
      target(std::forward<T>(arg));
  }
)";
        std::cout << "→ std::forward 根據 T 的推導結果決定轉發為左值或右值\n";
    }

    // ──────────────────────────────────────────
    // 4. 完美轉發的日誌包裝器
    // ──────────────────────────────────────────
    section("4. 通用日誌包裝器");
    {
        // 包裝計算函式
        auto add = [](int a, int b) { return a + b; };
        int result = logAndCall("add", add, 3, 4);
        std::cout << "  結果:" << result << "\n\n";

        // 包裝字串處理
        auto concat = [](const std::string& a, const std::string& b) {
            return a + b;
        };
        std::string s = logAndCall("concat", concat,
                                   std::string("Hello"), std::string(" World"));
        std::cout << "  結果:\"" << s << "\"\n\n";

        // 包裝 void 函式
        auto greet = [](const std::string& name) {
            std::cout << "  你好," << name << "!\n";
        };
        logAndCallVoid("greet", greet, std::string("C++ 學習者"));
    }

    // ──────────────────────────────────────────
    // 5. 工廠函式模式
    // ──────────────────────────────────────────
    section("5. 工廠函式(my_make_unique)");
    {
        // 傳入左值
        std::string name = "Alpha";
        std::cout << "--- 傳入左值 name ---\n";
        auto w1 = my_make_unique<Widget>(1, name);
        w1->info();
        std::cout << "name 仍然有效:\"" << name << "\"\n";

        // 傳入右值
        std::cout << "\n--- 傳入右值 ---\n";
        auto w2 = my_make_unique<Widget>(2, std::string("Beta"));
        w2->info();

        // 預設建構
        std::cout << "\n--- 無參數 ---\n";
        auto w3 = my_make_unique<Widget>();
        w3->info();

        std::cout << "\n--- 物件銷毀 ---\n";
    }

    // ──────────────────────────────────────────
    // 6. emplace_back vs push_back
    // ──────────────────────────────────────────
    section("6. emplace_back vs push_back");
    {
        std::vector<HeavyObject> vec;
        vec.reserve(4);  // 預留空間避免擴容

        // push_back 臨時物件 — 建構 + 移動
        std::cout << "--- push_back(HeavyObject{...}) ---\n";
        vec.push_back(HeavyObject{1, "Data-1"});

        // emplace_back — 直接在容器內建構(完美轉發參數)
        std::cout << "\n--- emplace_back(2, \"Data-2\") ---\n";
        vec.emplace_back(2, "Data-2");

        // push_back 具名物件 — 複製
        std::cout << "\n--- push_back(existing) ---\n";
        HeavyObject obj(3, "Data-3");
        vec.push_back(obj);

        // emplace_back 本質上等於:
        // new (&storage) T(std::forward<Args>(args)...);
        std::cout << "\nemplace_back 的優勢:\n";
        std::cout << "  - 避免建構臨時物件再移動/複製\n";
        std::cout << "  - 直接在容器的記憶體中就地建構\n";
        std::cout << "  - 底層使用完美轉發\n";

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

    // ──────────────────────────────────────────
    // 7. 多參數完美轉發
    // ──────────────────────────────────────────
    section("7. 可變參數模板 + 完美轉發");
    {
        // 通用的 construct 函式
        auto construct = [](auto&&... args) {
            return Widget(std::forward<decltype(args)>(args)...);
        };

        std::cout << "--- 用不同參數建構 Widget ---\n";
        auto w1 = construct();
        w1.info();

        std::string name = "Constructed";
        auto w2 = construct(42, name);
        w2.info();

        auto w3 = construct(99, std::string("RvalueConstructed"));
        w3.info();

        std::cout << "\n--- 離開作用域 ---\n";
    }

    // ──────────────────────────────────────────
    // 8. 完美轉發的常見模式
    // ──────────────────────────────────────────
    section("8. 完美轉發常見模式總結");
    {
        std::cout << "1. 工廠函式:\n";
        std::cout << R"(   template<typename T, typename... Args>
   auto create(Args&&... args) {
       return T(std::forward<Args>(args)...);
   }
)";

        std::cout << "\n2. 包裝函式 / 代理(Proxy):\n";
        std::cout << R"(   template<typename Func, typename... Args>
   decltype(auto) wrap(Func&& f, Args&&... args) {
       // 前置處理...
       decltype(auto) result = std::forward<Func>(f)(
           std::forward<Args>(args)...
       );
       // 後置處理...
       return result;
   }
)";

        std::cout << "\n3. 容器的 emplace 系列函式:\n";
        std::cout << R"(   template<typename... Args>
   void emplace_back(Args&&... args) {
       new (storage) T(std::forward<Args>(args)...);
   }
)";

        std::cout << "\n4. 建構子轉發:\n";
        std::cout << R"(   template<typename... Args>
   explicit Wrapper(Args&&... args)
       : inner_(std::forward<Args>(args)...) {}
)";
    }

    // ──────────────────────────────────────────
    // 9. 注意事項
    // ──────────────────────────────────────────
    section("9. 注意事項");
    {
        std::cout << "1. std::forward 只能用在轉發參考(T&&)的情境\n";
        std::cout << "2. 不要對同一個轉發參考呼叫兩次 std::forward\n";
        std::cout << "   (第二次可能存取到已移動的物件)\n";
        std::cout << "3. auto&& 也是轉發參考:\n";
        std::cout << R"(
   auto&& x = someFunc();  // x 的型別取決於 someFunc 的回傳值
   for (auto&& elem : container) { ... }  // 最泛化的 range-for
)";
        std::cout << "4. 轉發參考只出現在 template<typename T> 的 T&& 中\n";
        std::cout << "   Widget&& 不是轉發參考,它就是右值參考\n";
    }

    std::cout << "\n===== 完美轉發示範結束 =====\n";
    return 0;
}

Artículos relacionados