在讲解std::move和std::forward之前,我们必须先了解C++中左值、右值的相关概念。
1、左值、右值
(1)左值:一般指的是在内存中有对应的存储单元的值,最常见的就是程序中创建的变量。
(2)右值:一般指的是没有对应存储单元的值(寄存器中的立即数,中间结果等),例如一个常量,或者表达式计算的临时变量。
判断某个表达式是左值还是右值的方法:
(1)可位于 = 左侧的表达式就是左值;反之,只能位于 = 右侧的表达式就是右值。
// 其中x是一个左值,字面值5是一个右值
int x = 5;
// 错误,5 不能为左值
5 = x;
说明:C++中的左值也可以当作右值使用。
// y 是一个左值
int y = 10;
// x、y 都是左值,但可以将 y 可以当做右值使用
x = y;
(2)有名称的、可以获取到存储地址的表达式即为左值;反之则是右值。
上述示例中变量 x、y 是变量名且通过 &x 和 &y 可以获得他们的存储地址,因此 x 和 y 都是左值;反之,字面量 5、10,它们既没有名称,也无法获取其存储地址,因此 5、10 都是右值。
2、左值引用、右值引用
(1)左值引用:C++中采用&对变量进行引用,我们平时熟悉的引用就是左值引用。
int num = 10;
int& b = num; //正确
int& c = 10; //错误
const int& c = 10; //正确
(2)右值引用:
因为右值本身也没有对应的存储单元,所以没法进行引用。右值引用实际上只是一个逻辑上的概念,最大的作用就是让一个左值达到类似右值的效果(下面程序举例),让变量之间的转移更符合“语义上的转移”,以减少转移之间多次拷贝的开销。右值引用符号是&&。
注意:
(1)右值引用不支持引用左值;
(2)非常量右值引用可以引用的值的类型只有非常量右值;
(3)常量右值可以引用常量右值、非常量右值。
int num1 = 10;
const int num2 = 100;
int&& a = num1; //编译失败,非常量右值引用不支持引用非常量左值
int&& b = num2; //编译失败,非常量右值引用不支持引用常量左值
int&& c = 10; //编译成功,非常量右值引用支持引用非常量右值
const int&& d = num1; //编译失败,常量右值引用不支持引用非常量左值
const int&& e = num2; //编译失败,常量右值引用不支持引用常量左值
const int&& f = 100; //编译成功,常量右值引用支持引用右值
3、move()函数
move()函数将左值强制转换成右值。但move()并没有移动能力!
int num = 10;
int&& a = std::move(num); //编译成功
std::cout << a << std::endl; //输出结果为10;
std::cout << num << std::endl; //输出结果为10;move只是把左值强制转换成右值,并没有移动能力。
a = 20;
std::cout << num << std::endl; //输出20
为了加深对move()的印象,我们再来看一个例子:
std::string str1 = "I love C++";
std::string str2 = std::move(str1);
std::cout << "str2:" << str2 << std::endl;
std::cout << "str1:" << str1 << std::endl;
执行结果:
我们看到执行move()函数后,str1里的内容没了,难道被移走了?我们前面说过move()没有移动能力,造成str1的内容为空的原因是string的“移动构造函数”。 我们再来看一个例子:
std::string str3 = "I love C";
std::string&& def = std::move(str3);
std::cout << "str3:" << str3 << std::endl;
std::cout << "def:" << def << std::endl;
执行结果:
4、forward()函数
std::forward的作用是完美转发,如果传递的是左值转发的就是左值引用,传递的是右值转发的就是右值引用。
std::move可以减少不必要的拷贝开销,可以提高程序的效率。但是std::forward的作用是转发,左值引用转发成左值引用,右值引用还是右值引用,它的意义到底是什么?
原来是在程序的执行过程中,对于引用的传递实际上会有额外的隐式的转化,一个右值引用参数经过函数的调用转发可能会转化成左值引用,但这就不是我们希望看到的结果。
举例:
#include <iostream>
template<typename T>
void print(T& t) {
std::cout << "左值" << std::endl;
}
template<typename T>
void print(T&& t) {
std::cout << "右值" << std::endl;
}
template<typename T>
void testForward(T&& v) {
print(v);
print(std::forward<T>(v));
print(std::move(v));
}
int main(int argc, char* argv[])
{
testForward(1); // 传入右值
std::cout << "======================" << std::endl;
int x = 1;
testForward(x); // 传入左值
}
执行结果:
参考:
(1)[C++特性]对std::move和std::forward的理解
(2)【C++】左值和右值、左值引用(&)和右值引用(&&)
(3)抄袭李超老师的std::forward()