【C++】完美转发
1. 背景
假设我们有一个真正的目标函数 consume,它区分左值/右值:
void consume(int& x){ std::cout << "左值版本\n"; }
void consume(int&& x){ std::cout << "右值版本\n"; }
现在我们想写一个通用的调用consume的函数 relay,把所有实参原封不动地丢给 consume:
template<class T>
void relay(T arg) // ① 按值接收
{
consume(arg); // ② 局部变量arg是一个左值!
}
int a = 1;
relay(a); // 打印:左值版本 ✅
relay(42); // 打印:左值版本 ❌(本应右值)
问题就在这:
- 形参在函数体内永远是左值。这样传入函数的实参不论是左值or右值,形参接过后,在函数体内都作为左值处理。
- 那么若
consume区分左值版本、右值版本,在relay函数体内永远只会调用左值版本,哪怕传入relay的形参的是右值,我们希望调用的也是consume的右值版本。
而【万能引用+完美转发】的目的就是解决这个问题。
2. std::forward()
(2.1)完美转发
std::forward<T>(arg) 就是根据 T 的原始推导结果 做一次“强制类型转换”:
如果传入左值,则 T = int&,那么std::forward
如果传入右值,则T = int(这里要注意,传入右值时,T被编译器推导为),那么std::forward
这样在relay函数体内的arg经过forward的“强转”,可以还原为传入relay的形参的实参的类型(左值or右值),从而在relay内按照期望调用对应版本的函数。
传入右值时为什么不是 T = int&&?
C++ 标准规定:
“当形参是 forwarding reference(即 T&& 且 T 是模板参数)时,
如果实参是 左值 int& → T 推导为 int&
如果实参是 右值 int → T 推导为 int(不是 int&&)
然后再做引用折叠。”
这保证了:
T 永远是一个“裸类型”,不带引用;
真正的引用层级(& / &&)由形参的 T&& 和折叠规则自动生成。
(2.2)万能引用
- “万能引用”(universal / forwarding reference) 必须是 【模板参数】 且 形式恰好为 T&&(或 auto&&)。
一旦写成或因类模板的实例化而导致T变为某种具体类型(如 int&&、std::string&& 等),它就退化为 普通的右值引用,只能绑定右值,不再具备万能引用的“左右值通吃”能力。
template<class T>
void relay(T&& arg) // 这里的T&&不是右值引用,而是万能引用
{
consume(std::forward<T>(arg));
}
万能引用退化为普通右值引用的例子(一定要注意!!)
template <class T>
class ThreadStack
{
public:
bool push(T&& t)
{
std::lock_guard lg(this->m_mtx);
this->m_stk.push(std::forward<T>(t));
return true;
}
}
int main()
{
ThreadStack<int> ts;
for (int j = 0; j < 5; ++j)
{
osy << "thid=" << i << ", j=" << j << "\n";
ts.push(j);
}
}
// ThreadStack<int> 实例化后,签名变成:bool push(int&& t); //int&&普通右值引用,无法绑定左值j
//报错:“bool ThreadStack<int>::push(T &&)”: 无法将参数 1 从“int”转换为“T &&”
避免这种情况,给函数另一个不同于类模板的模板参数
template <typename U>
bool push(U&& t)
{
std::lock_guard lg(this->m_mtx);
this->m_stk.push(std::forward<T>(t));
return true;
}
- 万能引用只有在 模板实参由编译器推导 时才会触发引用折叠规则;一旦你把模板实参显式写出来,它就退化成普通右值引用,左值就绑定不了了。
//错误
//显式指定了int,则U&&变为int&&,j为左值传入报错
template <typename U>
bool push(U&& t)
{
std::lock_guard lg(this->m_mtx);
this->m_stk.push(std::forward<T>(t));
return true;
}
for (int j = 0; j < 5; ++j)
{
osy << "thid=" << i << ", j=" << j << "\n";
ts.push<int>(j);
}
(2.3)引用折叠
forward在还原类型时的规则
引用折叠:& && → &,&& && → &&,其余同理。
(2.4)实际使用案例
- std::vector::emplace_back
template<class... Args>
void vector::emplace_back(Args&&... args)
{
new (ptr) T(std::forward<Args>(args)...);
}
这个函数可以通过传入构造元素的参数,在vector就地构造,而不需先构造再拷贝。
但也可以传入右值此时,在emplace_back内部是调用元素类型的形参为右值的移动构造。
如:
vector<string> vec;
vec.emplace_back(std::string("hello"));
//string s("hello");
//vec.emplace_back(move(s));
而下面emplace_back则是在内部调用形参为左值的拷贝构造:
vector<string> vec;
string s("hello");
vec.emplace_back(s);

浙公网安备 33010602011771号