c++ 右值引用

1. 左值和右值

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

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

  i 是一个变量,有内存地址用于存储变量,是左值。2 在内存中无内存地址是右值。

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

  第6行,t 是左值,但是通过 & 可以将左值转化成右值,因为 &t 表示内存中一个地址,同第一个例子中的数字2一样,都是右值;

同理第5行错误,因为 t+1 是值,不是变量,所以无法通过 &(t+1) 取出一个地址当作右值。

    第八行,arr 作为右值(注意 arr 是数组内存地址)

      第九行可以看到打印的 arr 代表的地址内容

       第十行也就必定是错误的(是右值就不可能作为左值),r 是数组地址是右值,但是 arr 也是右值。

  而第11行,arr 通过 * 转换使得 *arr 成为左值 (在内存中有存储数的空降)。其中扮演关键角色的就是 * 解引用。

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;//copy constructor
59     cout << "=====end2=====" << endl << endl;
60 
61     A d;
62     cout << "=====start3=====" << endl;
63     d = A(10); // assignment 关于copy construct vs assignment construct
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;// copy construct
73     cout << "=====end2=====" << endl << endl;
74 
75     A d;
76     cout << "=====start3=====" << endl;
77     d = A(10);  // assignment construct 【两种构造区别】, A(10)是右值,d是左值
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 }

 

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

 

 

posted @ 2019-09-29 15:19  孔胡子  阅读(526)  评论(0编辑  收藏  举报