从 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 与引用折叠
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 中使用;应用边界应优先选择具体类型和明确所有权。