C++ 從基礎到精通 — 第 19 講
以完美轉送保留值類別
本部落格文章以教學為目的撰寫。 內容可能有誤,請搭配目標編譯器與標準函式庫文件確認。
即使理解移動語意,泛型包裝函式仍可能把所有引數都變成 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 中使用;應用程式邊界則優先使用具體型別與明確所有權。