← 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 与引用折叠

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