blog_why_std_forward
右值引用在用于表达式时是左值
-
初接触右值,面对下面代码可能会困惑
- 输入右值实参123, foo_no_move(int&& x)最终调用的却是bar(int &x),不是bar(int &&x)
foo_no_move(123); // ❌ 传入右值 → 输出"左值: 123" void foo_no_move(int &&x) { bar(x); } // ❌ x 是左值,调了 bar(int&)! void bar(int &x) { std::cout << "左值: " << x << "\n"; } void bar(int &&x) { std::cout << "右值: " << x << "\n"; } -
为什么?
- 因为调bar(x)时,不是按照x声明类型来选择重载版本的
- 在bar(x)这个表达式里面,x变量是左值、且丢失引用特性,类型是int
- 变量声明表示这个变量可以被移动(can),默认使用时是取值,而不是移动该变量
- std::move重新声明为可移动的,预示即将被移动(will),其实际的移动操作发生在移动构造、移动赋值
- 在bar(x)这个表达式里面,x变量是左值、且丢失引用特性,类型是int
- 为什么这样设计?
- 因为c++里面命名变量都是左值
- 因为在函数内部可能需要取右值引用的值,所以默认操作不能移动其状态,只能由程序员负责显示调用std::move再次将其转化为右值,再传递进资源转移的函数
- 参考:
- 右值引用变量在用于表达式时是左值
int&& x = 1; f(x); // 调用 f(int& x) f(std::move(x)); // 调用 f(int&& x) - 任何表达式的类型都是非引用类型
每个 C++ 表达式(带有操作数的操作符、字面量、变量名等)可按照两种独立的特性加以辨别:类型和值类别 (value category)。每个表达式都具有某种非引用类型。
- 右值引用变量在用于表达式时是左值
- 因为调bar(x)时,不是按照x声明类型来选择重载版本的
如何在多层函数传递参数的右值引用
- 使用std::move转发参数的左值或右值属性到内层函数时,每增加一个需要转发值类别的参数,外层需要重载的函数就增加2倍,是不可维护的
void foo(int &x) { bar(x); } void foo(int&& x) { bar(std::move(x)); } void bar(int &x) { std::cout << "左值: " << x << "\n"; } void bar(int &&x) { std::cout << "右值: " << x << "\n"; } - 有没有其他办法解决?
- 答案是用std::forward<T>()转发参数值类别(左值或右值属性),变长函数模板接收多参数