掌握std::move和std::forward

在讲解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()

猜你喜欢

转载自blog.csdn.net/mars21/article/details/131678322
今日推荐