C++右值引用与转移语义简要介绍

发布时间 2023-11-10 21:18:10作者: Koshkaaa

在 C++11 之前,值类型变量的传递会导致把它完整的拷贝一份

比如说把一个 vector 作为函数返回值赋值给某个局部变量,他就会调用 vector 的拷贝构造函数创建一个完整的副本,把这个副本作为函数返回的临时变量,然后把这个临时变量赋值给那个局部变量时又会再次拷贝构造
(不过这其实会被大多数编译器优化掉)

右值

上面例子中的临时变量就是一个右值

右值是无法获取到地址的值
大概可以理解为临时的变量或者没有被声明但是实际存在的变量

函数的返回值,还有常量都是右值

右值引用

根据名字我们知道它是一个右值的引用(废话)

使用右值引用接收右值可以让右值不被销毁,同时也不会拷贝任何数据

举个例子,下面代码中 str 变量接收了 GetStr 函数返回的右值

string GetStr() {
    return "Hello world";
}

int main() {
    string&& str = GetStr();
    return 0;
}

转移构造函数

因为右值被使用过后就会被销毁,所以完整拷贝它的备份是很不划算的
我们希望把右值里的数据直接偷出来

那么我们可以通过转移构造函数来实现

class Array {
   private:
    int* arr;
    int len;

   public:
    Array(int len) : arr(new int[len]) {}
    ~Array() {
        if (arr) delete[] arr;
    }
    Array(const Array& other) : len(other.len) {
        // 拷贝构造会导致整个数组被复制
        arr = new int[other.len];
        memcpy(arr, other.arr, len * sizeof(int));
    }
    Array(Array&& other) : arr(other.arr) {
        // 转移构造可以直接把数组的指针偷过来
        other.arr = NULL;
    }
};

可以看到上面的代码把 other 这个右值里的 arr 偷走了

另外类似的还有转移赋值函数,也是使用右值引用传参,然后在函数里把右值里的东西偷出来

转移语义

那么又有一个问题,如果有一个左值,他以后不会再被用到,我希望能把它里面的东西偷出来放到另一个左值里,怎么实现

使用 std::move,把一个左值转换为右值,然后把它里面存的东西搬空

看这个例子就很清晰了

class Array {
   private:
    int* arr;
    int len;

   public:
    Array(int len) : arr(new int[len]) {}
    ~Array() {
        if (arr) delete[] arr;
    }
    Array(const Array& other) : len(other.len) {
        // 拷贝构造会导致整个数组被复制
        arr = new int[other.len];
        memcpy(arr, other.arr, len * sizeof(int));
    }
    Array(Array&& other) : arr(other.arr) {
        // 转移构造可以直接把数组的指针偷过来
        other.arr = NULL;
    }
};

int main() {
    Array a(100);
    Array b(move(a));
    return 0;
}

可以看到变量 a 里存的数组指针被变量 b 偷出来了,从而避免了整个数组的拷贝