C++11 右值引用&&、移动语义std::move、完美转发std::forward

发布时间 2023-08-26 17:09:35作者: htj10

参考:https://blog.csdn.net/HR_Reborn/article/details/130363997

 

#pragma once
class Array {
public:
    Array() : size_(0), data_(nullptr){

    }
    Array(int size) : size_(size) {
        data_ = new int[size_];
    }

    // 复制构造函数 (深拷贝构造)
    Array(const Array& temp_array) {
        size_ = temp_array.size_;
        data_ = new int[size_];
        for (int i = 0; i < size_; i++) {
            data_[i] = temp_array.data_[i];
        }
    }

    // 赋值构造函数 (深拷贝赋值)
    Array& operator=(const Array& temp_array) {
        delete[] data_;

        size_ = temp_array.size_;
        data_ = new int[size_];
        for (int i = 0; i < size_; i++) {
            data_[i] = temp_array.data_[i];
        }
        //返回对象的引用,是为了做链式处理: f1 = f2 = f3;
        return *this;
    }

    // 移动构造函数
    Array(Array&& temp_array) {
        cout << "移动构造函数\n";
        data_ = temp_array.data_;
        temp_array.data_ = nullptr;// 为防止temp_array析构时delete data,提前置空其data_
    }

    // 移动赋值构造函数
    Array& operator=(Array&& temp_array) {
        cout << "移动赋值构造函数\n";
        data_ = temp_array.data_;
        temp_array.data_ = nullptr;// 为防止temp_array析构时delete data,提前置空其data_
        return *this;
    }


    ~Array() {
        delete[] data_;
    }

public:
    int *data_;
    int size_;
};


// --> 测试 完美转发
void B(int&& ref_r) {
    ref_r = 1;
}

// A、B的入参是右值引用
// 有名字的右值引用是左值,因此ref_r是左值
void A(int&& ref_r) {
    //B(ref_r);  // 错误,B的入参是右值引用,需要接右值,ref_r是左值,编译失败

    B(std::move(ref_r)); // ok,std::move把左值转为右值,编译通过
    B(std::forward<int>(ref_r));  // ok,std::forward的T是int类型,属于条件b,因此会把ref_r转为右值
}

void func1(int& i){
    cout << "参数是左值," << i << endl;
}
void func1(int&& i){
    cout << "参数是右值," << i << endl;
}

void func(int&& i){
    //注意:形参 i是左值
    func1(i);//这样怎么都是调用的 左值引用的 函数。为解决,用完美转发std::forward
}

// 1.如果模板中(包括类模板、函数模板)的参数为 T&& 参数名,那么函数既可以接受左值引用,也可以接受右值引用。
//2. std::forward<T>(参数), 用于转发参数。若参数是左值,转发后仍然是左值,若参数是右值,转发后仍然是右值。
template<class T>
void func(T&& t){
    func1(std::forward<T>(t));
}

// <-- 测试 完美转发

class TestRightValueRef
{
public:
    TestRightValueRef() = delete;
    ~TestRightValueRef() = delete;

    static void testAll(){
        //test02();
        test03();
    }
    static int foo(){ return 0; }
    static void test01(){

        /*
        左值,lvalue,就是赋值符号左边的值,如a=5,a就是左值,但是准确的来说,左值是表达式(不一定是赋值表达式)后仍然存在的持久对象。
        右值,rvalue,右边的值,是指表达式结束就不存在的临时对象。
            纯右值,prvalue,用于计算的或者用于初始化对象的的右值。例如 int a = 5; // 5是右值  注意 字符串 "abc" 这个不是右值,是指针
            ( 在介绍将亡值之前对左右和右值做个总结,有地址的变量就是左值,没有地址的字面值、临时值就是右值。)
            将亡值,xvalue, 是C++11为了引入右值引用而提出的概念,与纯右值的不同点在于,将亡值是即将被销毁、却能够被移动的值。例如,函数中返回的对象。
        有地址的变量就是左值,没有地址的字面值或临时值就是右值。
        */

        /*
        左值引用:只能指向左值。不能指向右值的就是左值引用
        右值引用:只能指向右值,不能指向左值,符号是&&
        */

        int a = 5;// a 是左值, 5是右值
        int b = foo();// b 是左值, foo()是右值

        int& ra = a;        //左值引用
        int& rb = b;        //左值引用
        int&& rc = 5;        //右值引用
        int&& rd = foo();    //右值引用

        //int& r = 5;            //不能指向右值,编译报错
        //int&& r = a;            //不能指向左值,编译报错

        //但const 左值引用  都可以
        const int& rf1 = a;
        const int& rf2 = 3;
        const int& rf3 = foo();
        //为什么?因为const 左值引用不会修改指向值,因此可以指向右值,这也是为什么要使用const &作为函数参数的原因之一,如std::vector的push_back:
        //void push_back(const value_type& val);
        //如果没有const,vec.push_back(5)这样的代码就无法编译通过了。

        //左值引用默认只能指向左值,但是加了const的情况下可以指向右值,那么右值引用有没有类似的机制来指向左值呢?有的,就是std::move。
        //std::move 唯一的功能是把左值强制转换为右值,可以让右值引用指向左值,等于一个强制类型转换。
        int&& rg = std::move(a);
        
        //注意,被声明出来的左、右值引用都是左值,因为被声明出的左右值引用是有地址的,也位于等号左边。
        // 所以,ra,rb,rc,rd 都是左值


        /*
总结:

    从性能上讲,左右值引用没有区别,传参使用左右值引用都可以避免拷贝。
    右值引用可以直接指向右值,也可以通过std::move指向左值;而左值引用只能指向左值(const左值引用也能指向右值)。
    作为函数形参时,右值引用更灵活。虽然const左值引用也可以做到左右值都接受,但它无法修改,有一定局限性

    void f(const int& n) {
        n += 1; // 编译失败,const左值引用不能修改指向变量
    }
     
    void f2(int && n) {
        n += 1; // ok
    }
     
    int main() {
        f(5);
        f2(5);
    }
————————————————
版权声明:本文为CSDN博主「HR_Reborn」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/HR_Reborn/article/details/130363997
        */


    }


    //在实际场景中,右值引用和std::move被广泛应用于在STL和自定义类中实现移动语义,避免拷贝,从而提升程序性能。
    static void test02(){
        // 例1:Array用法
        Array a(2);

        // 做一些操作
        //.....

        // 左值a,用std::move转化为右值
        Array b(std::move(a));//会调用移动构造函数
        //std::move(a) 后 a 不应再被使用,会报错。因为nullptr
        //b = a;    //error

        Array c;
        Array d;
        c = d;//调用赋值构造函数

        c = std::move(d);//调用移动赋值构造函数

    }

    static void test03(){
        //完美转发
        /*
        完美转发std::forward

        和std::move一样,std::forward本质上是进行类型转换,即左值右值之间的转换,与move相比,forward更强大,move只能转出来右值,forward都可以。

        std::forward<T>(u)有两个参数:T与 u。 
        a. 当T为左值引用类型时,u将被转换为T类型的左值; 
        b. 否则u将被转换为T类型右值。

        举个例子,有main,A,B三个函数,调用关系为:main->A->B。

        */
        int a = 5;
        A(std::move(a));

        cout << "-------------\n";
        func(a);
        func(5);
    }
};