• c++ 右值引用


    1. 左值和右值

      简单的定义来说,能够放在赋值等号左边的就是左值,反之则是右值(所有表达式不是左值就是右值,左右值不存在交集)——但是这个解释实在有点鸡肋。下面对定义结合例子做些补充。

    • 右值:在内存中不占有内存的表达式
    • 左值:在内存中占有一定内存位置的表达式
    1 int i;
    2 i = 2; //合法
    3 2 = i; //非法

      例子合法性很好理解——可以在结合左右值的定义。

     1 #include<iostream>
     2 int main()
     3 {
     4     int t;
     5     int* q = &(t + 1);//非法,t+1在内存中没有位置
     6 
     7     int arr[] = { 1,2,3 }; int r[] = { 1,2,3 };
     8     int* i = arr; //合法
     9     std::cout << arr << std::endl; // 006FF808
    10     arr = r; // 非法
    11     *(arr) = 10;//合法
    12 }

      在这里,我们需要理解下第八行,arr 作为右值—— 注意 arr 是内存地址,是一个地址,不同于第一个例子中的 i 。在第九行可以看到打印的 arr 代表的地址内容,第十行也就必定是错误的(是右值就不可能作为左值)。

      而第11行,arr 貌似变成了左值——也就是左值和右值在一定程度上是可以转换的。其中扮演关键角色的就是 * 解引用。既然是左值,按照我们前面说的,那应该在内存中存在一处分配的内存位置,*arr 解引用之后的确是存了 1 这个数,所以可以重新赋值10。

    2. 右值引用

      通过下面的例子来理解:

     1 #include<iostream>
     2 #include<string>
     3 using namespace std;
     4 struct A
     5 {
     6     // 构造函数
     7     explicit A(size_t _size = 0) :size(_size),ptr(new int[_size]()) {
     8         cout << "construct..."<< endl;
     9     }
    10 
    11     // 拷贝赋值运算符
    12     A& operator=(const A& tmp) {
    13         if (&tmp != this)
    14         {
    15             this->ptr = new int[tmp.size];
    16             for (size_t i = 0; i < tmp.size; ++i) {
    17                 this->ptr[i] = tmp.ptr[i];
    18             }
    19         }
    20         cout << "copy assignment 等于..." << endl;
    21         return *this;
    22     }
    23 
    24     // 拷贝构造函数
    25      A(const A& tmp) {
    26         this->ptr = new int[tmp.size];
    27         for (size_t i = 0; i < tmp.size; ++i) {
    28             this->ptr[i] = tmp.ptr[i];
    29         }
    30         cout << "copy construct..." << endl;
    31     }
    32 
    33     // 析构函数
    34     ~A() {
    35         if (ptr) 
    36         {
    37             delete[] ptr;
    38             size = 0;
    39             cout << "destructor..." << endl;
    40         }
    41     }
    42 
    43 private:
    44     int* ptr;
    45     size_t size;
    46 };
    47 
    48 int main()
    49 {
    50     A a(10);
    51     A b;
    52 
    53     cout<< "=====start=====" <<endl;
    54     b = a;
    55     cout << "=====end=====" << endl << endl;
    56 
    57     cout << "=====start2=====" << endl;
    58     A c = a;//是拷贝构造。或者加上explicit改成A c(a) 可能更好理解
    59     cout << "=====end2=====" << endl << endl;
    60 
    61     A d;
    62     cout << "=====start3=====" << endl;
    63     d = A(5);
    64     cout << "=====end3=====" << endl << endl;
    65 
    66     return 0;
    67 }

       结果上看,应该都比较好理解。

      其中第三处是要引出的重点,可以看到应该使用了 d = A(10),这条语句,A() 是一个临时变量,在完成 construct -> copy assignment 之后就进行了 destructor。我们来讨论下这一句赋值语句的内部过程,A(10) 调用构造函数并申请了内存空间,然后去实现拷贝赋值运算符,临时变量A(10)的内容【copy】给 d 变量,随后 A(10) 被销毁。而我们知道在 copy assignment (拷贝赋值运算符)方法中,我们又为 d 变量同样申请了 大小为10的数组空间,这就出现了一定的“低效处理”。不妨这样想,如果我们将A(10)申请的空间,通过拷贝赋值运算符,直接将这个空间给变量d,这样d就不用再去申请空间(反正A(10)的空间申请了,马上就会释放。相当于白白多申请和释放了一次,不如直接将A(10)的空间转移到d变量名下)。

      这就引入了右值引用,如下例子:

     1 #include<iostream>
     2 #include<string>
     3 using namespace std;
     4 struct A
     5 {
     6     // 构造函数
     7     explicit A(size_t _size = 0) :size(_size),ptr(new int[_size]()) {
     8         cout << "construct..."<< endl;
     9     }
    10 
    11     // 拷贝赋值运算符
    12     A& operator=(const A& tmp) {
    13         if (&tmp != this)
    14         {
    15             this->ptr = new int[tmp.size];
    16             for (size_t i = 0; i < tmp.size; ++i) {
    17                 this->ptr[i] = tmp.ptr[i];
    18             }
    19         }
    20         cout << "copy assignment 等于..." << endl;
    21         return *this;
    22     }
    23 
    24     // 使用右值引用的拷贝赋值运算符
    25     A& operator=(A&& tmp) {
    26         if (&tmp != this)
    27         {
    28             this->ptr = tmp.ptr;
    29             this->size = tmp.size;
    30 
    31             tmp.ptr = nullptr;
    32             tmp.size = 0;
    33         }
    34         cout << "copy assignment 等于2..." << endl;
    35         return *this;
    36     }
    37 
    38     // 拷贝构造函数
    39      A(const A& tmp) {
    40         this->ptr = new int[tmp.size];
    41         for (size_t i = 0; i < tmp.size; ++i) {
    42             this->ptr[i] = tmp.ptr[i];
    43         }
    44         cout << "copy construct..." << endl;
    45     }
    46 
    47     // 析构函数
    48     ~A() {
    49         if (ptr) 
    50         {
    51             delete[] ptr;
    52             size = 0;
    53             cout << "destructor..." << endl;
    54         }
    55     }
    56 
    57 private:
    58     int* ptr;
    59     size_t size;
    60 };
    61 
    62 int main()
    63 {
    64     A a(10);
    65     A b;
    66 
    67     cout<< "=====start=====" <<endl;
    68     b = a;
    69     cout << "=====end=====" << endl << endl;
    70 
    71     cout << "=====start2=====" << endl;
    72     A c = a;//是拷贝构造。或者加上explicit改成A c(a) 可能更好理解
    73     cout << "=====end2=====" << endl << endl;
    74 
    75     A d;
    76     cout << "=====start3=====" << endl;
    77     d = A(10);
    78     cout << "=====end3=====" << endl << endl;
    79 
    80     return 0;
    81 }

       可以看到通过使用右值引用,执行一样的 d = A(5)操作会调用右值引用的拷贝运算符方法。这样写就能达到如下效果:A(5)申请了临时空间,但是这空间通过拷贝运算符方法(右值引用)直接被 d 变量接管,d 变量无需再去申请空间了注意:第31,32行代码不能不写,经过28,29行之后,d 变量和 A(5) 临时变量的指针都指向了同一块内存,而之后 A(5) 将被销毁,所以需要将 A(5) 的指针置为 nullptr,但这部分内存由 d 对象接管。注意:这里看上去好像少了临时变量 A(5) destructor(没有打印),其实对象 A(5) 也是执行了析构,析构时释放这个类——也就是临时A(5)这个类,但是因为我析构函数有个if条件,所以只是看上去没有打印罢了,反正依旧会执行析构。

    3. std::move()

       我们先看下面的例子:

     1 #include<iostream>
     2 using namespace std;
     3 void f(int&& i) {
     4     cout << "右值引用" << endl;
     5 }
     6 
     7 void f(int& i) {
     8     cout << "左值引用" << endl;
     9 }
    10 
    11 int main()
    12 {
    13     f(4); // 右值引用
    14     int i = 3;
    15     f(i); // 左值引用
    16     f(int());// 右值引用.int() 调用默认构造函数,返回 0 值
    17 
    18     f(std::move(i));  // 右值引用
    19     f(std::move(3)); // 左值引用
    20 }

      因为我们已经知道了如果分辨左值和右值,所以输出应该都在意料之内。

      但是请比较第15行 和 第18行,可以看到同一个变量 i 怎么还能输出不同的结果?其中的关键就是 std::move(),请注意:std::move() 并不是如上文提到的内存接管,而是将 左值->变换成-> 右值。

    3.1 实例一:移动构造函数 

     1 #include<iostream>
     2 using namespace std;
     3 class Test
     4 {
     5 public:
     6     Test()
     7     {
     8         cout << "constructor " << endl;
     9     }
    10 
    11     Test(const Test& t)
    12     {
    13         cout << "copy constructor " << endl;
    14     }
    15 
    16     Test& operator = (const Test& t)
    17     {
    18         cout << "copy = constructor" << endl; return *this;
    19     }
    20 
    21     Test(Test&& t)   
    22     {
    23          cout << "move constructor" << endl;
    24     }
    25 
    26     Test& operator =(Test&& t)
    27     {
    28 
    29         cout << "move = constructor" << endl; return *this;
    30     }
    31 
    32     ~Test()
    33     {
    34         cout << "destructor " << endl;
    35 
    36     }
    37 };
    38 
    39 int main()
    40 {
    41     Test a;
    42     cout << "start " << endl;
    43     Test b = std::move(a);  
    44     cout << "end " << endl << endl;
    45 
    46     cout << "start2 " << endl;
    47     Test c = a;   
    48     cout << "end2 " << endl << endl;
    49     return 0;
    50 }

     

      注意:如果没有 21~24行的移动构造函数,则结果如下,再构造 Test b 的时候会使用赋值构造函数

    3.2 实例二 移动赋值函数

      改变main 函数中的两句话:

     1 #include<iostream>
     2 using namespace std;
     3 class Test
     4 {
     5 public:
     6     Test()
     7     {
     8         cout << "constructor " << endl;
     9     }
    10 
    11     Test(const Test& t)
    12     {
    13         cout << "copy constructor " << endl;
    14     }
    15 
    16     Test& operator = (const Test& t)
    17     {
    18         cout << "copy = constructor" << endl;
    19         return *this;
    20     }
    21 
    22     Test(Test&& t)   
    23     {
    24          cout << "move constructor" << endl;
    25     }
    26 
    27     Test& operator =(Test&& t)
    28     {
    29 
    30         cout << "move = constructor" << endl;
    31         return *this;
    32     }
    33 
    34     ~Test()
    35     {
    36         cout << "destructor " << endl;
    37 
    38     }
    39 };
    40 
    41 int main()
    42 {
    43     Test a;
    44     Test b;
    45     cout << "start " << endl;
    46     b = std::move(a);
    47     cout << "end " << endl << endl;
    48 
    49     Test c;
    50     cout << "start2 " << endl;
    51     c = a;
    52     cout << "end2 " << endl << endl;
    53     return 0;
    54 }

    完结,撒花。*★,°*:.☆( ̄▽ ̄)/$:*.°★* 。

     

  • 相关阅读:
    css3新特性
    线程间通信的三种方法
    硬件相关知识
    time.h
    ldr指令总结
    你不知道的100个小秘密
    ARM学习日记
    C中位域的使用
    《编程之美》第2刷勘误
    排序2
  • 原文地址:https://www.cnblogs.com/KongHuZi/p/11608061.html
Copyright © 2020-2023  润新知