← SEARCHABLE ARCHIVE
C / C++ · 2026-08-13

C++ 從基礎到精通 — 第 19 講

以完美轉送保留值類別

INTERACTIVE READING MODE →

本部落格文章以教學為目的撰寫。 內容可能有誤,請搭配目標編譯器與標準函式庫文件確認。

即使理解移動語意,泛型包裝函式仍可能把所有引數都變成 lvalue。Perfect forwarding 能在呼叫下一個函式時保留呼叫者傳入的值類別。

有名稱的 expression 是 lvalue

void Store(const Texture& texture);
void Store(Texture&& texture);

Texture loaded = LoadTexture();
Store(loaded);              // lvalue
Store(std::move(loaded));   // rvalue
Store(LoadTexture());       // rvalue

lvalue 代表持續存在的物件,rvalue 可代表暫時值或明確允許移動的物件。即使變數型別是 Texture&&,以名稱使用它時,該 expression 仍是 lvalue。

Forwarding reference 與 reference collapsing

template <typename T>
void Register(T&& value)
{
    Store(value);
}

當 T 由呼叫推導時,此 T&& 是 forwarding reference。傳入 lvalue 時,T 會推導為 lvalue reference;傳入 rvalue 時則保留 rvalue reference。

但函式內的 value 有名稱,因此 Store(value) 永遠選擇 lvalue 多載。

用 std::forward 還原值類別

template <typename T>
void Register(T&& value)
{
    Store(std::forward<T>(value));
}

std::forward<T> 將原本的 lvalue 保持為 lvalue,原本的 rvalue 保持為 rvalue。它不像 std::move 一律轉成 rvalue。

template <typename T, typename... Args>
std::unique_ptr<T> MakeOwned(Args&&... args)
{
    return std::make_unique<T>(std::forward<Args>(args)...);
}

Factory 可用此模式透明傳遞建構子引數而不加入不必要的複製。

並非所有 API 都需要 forwarding reference

void Player::SetName(std::string name)
{
    Name = std::move(name);
}

儲存字串的 setter 以值接收再移動一次通常更單純。Forwarding reference 接受的型別非常廣,也會讓診斷更複雜。只在真正透明的泛型 adapter 中使用;應用程式邊界則優先使用具體型別與明確所有權。