右值引用与move

C++11的右值引用与std::move

c++引入的一个重要的概念是右值引用,这里先简单的说明下左值和应用的区别:

int value = 2;
int& leftRef = value; // 左值
int& leftRefToRight = value +2; // 错误!!! value +2是一个临时变量,称为右值,即只能放在=右边。
int&& rightRef = value +2; // && 右值引用

这里涉及到一个重要的概念是生命周期,a+2作为一个右值,其生命周期是非常短暂的,如果按照一般的做法:

int value = 1;
auto valueCopy = value +2;

上诉方法会造成多次构造函数调用,在特定场景下会由严重的性能问题,通过右值引用能够避免构造函数调用。

既然右值引用可以减少无用的copy,那么在一般赋值时如何优化性能呢,这也是std::move的意义:

int value = 1;
auto&& ref = std::move(value);

通过使用std::move可以对一个左值进行右值引用。到目前位置,一切看起来很正常。

但是如该深究下去,就会发现一个无法解决的矛盾,使用std::move后,value的所有权已经被转移,那么就是说value应该已经被销毁,但是实际情况完全不是这样:

#include <iostream>
#include <utility>
#include <vector>
#include <string>

int main() {
    int intValue = 1;
    int&& intValueRef = std::move(intValue);

    std::string stringValue  = "Hello world";
    std::string&& stringValueRef  = std::move(stringValue);
    std::string stringTarget  = std::move(stringValue);

    std::cout << "value of intValue:\t\t" << intValue << std::endl
              << "value of intValueRef:\t\t" << intValueRef << std::endl
              << "value of stringValue:\t\t" << stringValue << std::endl
              << "value of stringValueRef:\t" << stringValueRef << std::endl
              << "value of stringTarget:\t\t" << stringTarget << std::endl;

    // you can set change a event you move it!!!
    intValue = 2;
    stringValue = "I changed";

    std::cout << "change values"<<std::endl;

    std::cout << "value of intValue:\t\t" << intValue << std::endl
              << "value of intValueRef:\t\t" << intValueRef << std::endl
              << "value of stringValue:\t\t" << stringValue << std::endl
              << "value of stringValueRef:\t" << stringValueRef << std::endl
              << "value of stringTarget:\t\t" << stringTarget << std::endl;

    // change ref
    intValueRef = 3;
    stringValueRef = "ref changed";

    std::cout << "change refs"<<std::endl;

    std::cout << "value of intValue:\t\t" << intValue << std::endl
              << "value of intValueRef:\t\t" << intValueRef << std::endl
              << "value of stringValue:\t\t" << stringValue << std::endl
              << "value of stringValueRef:\t" << stringValueRef << std::endl
              << "value of stringTarget:\t\t" << stringTarget << std::endl;

    return 0;
}

这个复杂的实例输出为:

value of intValue:              1
value of intValueRef:           1
value of stringValue:
value of stringValueRef:
value of stringTarget:          Hello world
change values
value of intValue:              2
value of intValueRef:           2
value of stringValue:           I changed
value of stringValueRef:        I changed
value of stringTarget:          Hello world
change refs
value of intValue:              3
value of intValueRef:           3
value of stringValue:           ref changed
value of stringValueRef:        ref changed
value of stringTarget:          Hello world

可以看出,在执行完std::move后,value的生命周期还是可以的使用的,这在没有处理正确的情况下会导致严重的问题。
简单来说,C++里面的move并不是move, 只是一个性能优化的“copy”方法,千万不要对他产生大胆的想法。
或许这个函数改个名字对大家都会友好一些。

对与rust来说,就没有这个有歧义的设计了,rust的复制默认情况下都是执行的move,而且一旦move后就直接销毁原对象的生命周期,在编译阶段就避免了错误的发生:

fn main() {
    let x = String::from("hello world");
    let y = x;
    // println!("{}", x);  // error,x 已经被move给x了,请使用y
    println!("{}", y); // ok
}