Allen's Blog

View on GitHub
7 May 2024

Rvalue References

by Allen Sun

1 值的类型(The value categories)

2 引用

引用即变量的别名,底层用指针实现。引用存在的意义是:传参时避免拷贝

3 移动语义(Move Semantices)

std::move()

虽然名字叫move,但背地里只做了类型转换,将move的参数转换成右值。

template <class T>
typename remove_reference<T>::type&&
move(T&& a){
    return a;
}

问题分析

/* 代码A */
class Array{
public:
    Array(int size): size_(size){
        data_ = new int[size_];
    }
    ~Array(){
        delete []data_;
    }
    Array(const Array& other){
        size_ = other.size_;
        data_ = new int[size_];
        for(int i = 0; i < size_; i++){
            data_[i] = other.data_[i];
        }
    }
    Array& operator=(const Array& other){
        delete []data_;
        size_ = other.size_;
        data_ = new int[size_];
        for(int i = 0; i < size_; i++){
            data_[i] = other.data_[i];
        }
    }
public:
    int* data_;
    int size_;
};
/* 代码B */
class Array{
public:
    // 左值引用——拷贝构造
    Array(const Array& other){
        size_ = other.size_;
        data_ = new int[size_];
        for(int i = 0; i < size_; i++){
            data_[i] = other.data_[i];
        }
    }
    // 左值引用——拷贝构造函数重载
    Array(const Array& other, bool move){
        size_ = other.size_;
        data_ = other.data_;
        other.data_ = nullptr;  // error:无法修改const修饰的参数
    }
};
/* 代码C */
class Array{
public:
    // 深拷贝,左值引用,《拷贝构造》
    Array(const Array& other){
        size_ = other.size_;
        data_ = new int[size_];
        for(int i = 0; i < size_; i++){
            data_[i] = other.data_[i];
        }
    }
    // 浅拷贝,右值引用,《移动构造》
    Array(Array&& other){
        size_ = other.size_;
        data_ = other.data_;
        other.data_ = nullptr;  // 防止other析构data_指向的资源
    }
};

典例赏析

提醒:不是std::move()提高了性能,而是通过std::move()做类型转换后,调用到移动构造/赋值函数而非拷贝构造/赋值函数,从而提高了性能。

  1. case1:移动构造

     std::string str = "I've already learned what is Move Semantics.";
     std::vector<std::string> vec;
    
     vec.push_back(str);  // 调用拷贝构造
    
     /* 调用移动构造,str失去原有值变成空串 */
     vec.push_back(std::move(str));
    
  2. case2:移动赋值

     std::unique_ptr<A> ptr = std::make_unique<A>();
    
     /* 调用移动赋值,ptr失去原有资源的所有权变成空指针 */
     std::unique_ptr<A> ptr1 = std::move(ptr);
    
     std::unique_ptr<A> ptr2 = ptr;  // error:unique_ptr不支持拷贝赋值
    

4 完美转发(Perfect Forwarding)

std::forward()

并不做转发,做类型转换,比std::move()的功能更强大。主要用于模板编程的参数转发。

典例赏析

void func1(int& ref_l){
    ref_l = 1;
}

void func2(int&& ref_r){
    ref_r = 1;
}

void foo(int&& ref_r){
    func1(ref_r);  // ok, 右值引用ref_r是左值
    func1(std::forward<int&>(ref_r));  // ok, std::forward会将ref_r转换为左值

    func2(ref_r); // error:func2需要的参数是右值
    func2(std::move(ref_r));  // ok, std::move将ref_r转换为右值
    func2(std::forward<int&&>(ref_r));  // ok, std::forward将ref_r转换为右值
}
tags: C++ - Rvalue - Move-Semantics