左值引用、右值引用和移动语义

【1】左值引用和右值引用

左值引用(一般所谓的引用形式)使标识符关联到左值。

何为左值?左值是一个表示数据的表达式(如变量名、解除引用的指针)。

最初,左值可出现在赋值语句的左边,但修饰符const的出现使得可以声明这样的标识符(即不能给它赋值,但可获取其地址)。

 1     int n = 10;
 2     int * pt = new int;
 3     int & rn = n;  // 变量名
 4     int & rt = *pt;  // 解除引用的指针
 5     const int b = 20;  // 不可以给b赋值,但可获取其地址
 6     // b = 21;  // error
 7     const int * pb = &b;  // ok
 8     const int & rb = b;  // 不可以给rb赋值,但可获取其地址
 9     // rb = n;    // error
10     const int * prb = &rb;  // ok

总而言之,判断左值的唯一条件是程序可获取其地址(即可对其应用地址运算符)。

右值引用使标识符关联到右值。右值引用是使用&&表示的。右值即可出现在赋值表达式右边,但不能对其应用地址运算符的值(与左值相反)。

右值包括字面常量(C-风格字符串除外,因为它表示地址),比如x+y等表达式以及返回值的函数(条件是该函数返回的不是引用)。

举例如下:

1     int x = 10;
2     int y = 11;
3     int && r1 = 12;  // 常量
4     int && r2 = x + y;  // 表达式
5     double && r3 = std::sqrt(2.0); // 函数返回值

通过示例可以发现:将右值关联到右值引用导致该右值被存储到特定的位置,并且可通过标识符获取该位置的地址。也就是说,虽然不可将地址运算符&应用于12,但可将其用于r1。将数据与特定的地址关联,使得可通过右值引用来访问该数据信息。

下面演示及验证右值引用的作用:

 1 #include <iostream>
 2 using namespace std;
 3 
 4 double f(double tf) { return tf/20; };
 5 
 6 void main()
 7 {
 8     double tc = 10.5;
 9     double && rd1 = 100.01;
10     double && rd2 = 1.8 * tc;
11     double && rd3 = f(rd2);
12     cout << "tc Value And Address: " << tc << "  " << &tc << endl;
13     cout << "rd1 Value And Address: " << rd1 << " " << &rd1 << endl;
14     cout << "rd2 Value And Address: " << rd2 << "  " << &rd2 << endl;
15     cout << "rd3 Value And Address: " << rd3 << "  " << &rd3 << endl;
16     cin.get();
17 }
18 // run out
19 /*
20 tc Value And Address: 10.5  003FFAE4
21 rd1 Value And Address: 100.01 003FFAC8
22 rd2 Value And Address: 18.9  003FFAAC
23 rd3 Value And Address: 0.945  003FFA90
24 */

为啥需要右值引用呢?引入右值引用的主要目的之一是实现移动语义。

【2】移动语义之移动构造函数

2.1 为何需要移动语义?

请看如下示例代码(相信你我已经写过很多类似的),只有复制构造函数情况下:

  1 // 例1:只有复制构造函数
  2 #include <iostream>
  3 using namespace std;
  4 
  5 // interface
  6 class Useless
  7 {
  8 private:
  9     int n;          // number of elements
 10     char * pc;      // pointer to data
 11     static int ct;  // number of objects
 12     void ShowObject() const;
 13 
 14 public:
 15     Useless();
 16     explicit Useless(int k);
 17     Useless(int k, char ch);
 18     Useless(const Useless & f); // regular copy constructor
 19     ~Useless();
 20     Useless operator+(const Useless & f)const;
 21     void ShowData() const;
 22 };
 23 
 24 // implementation
 25 int Useless::ct = 0;
 26 
 27 Useless::Useless()
 28 {
 29     ++ct;
 30     n = 0;
 31     pc = nullptr;
 32     cout << "default constructor called; number of objects: " << ct << endl;
 33     ShowObject();
 34 }
 35 
 36 Useless::Useless(int k) : n(k)
 37 {
 38     ++ct; 
 39     cout << "Useless(int k) constructor called; number of objects: " << ct << endl;
 40     pc = new char[n];
 41     ShowObject();
 42 }
 43 
 44 Useless::Useless(int k, char ch) : n(k)
 45 {
 46     ++ct;
 47     cout << "Useless(int k, char ch) constructor called; number of objects: " << ct << endl;
 48     pc = new char[n];
 49     for (int i = 0; i < n; i++)
 50         pc[i] = ch;
 51     ShowObject();
 52 }
 53 
 54 Useless::Useless(const Useless & f) : n(f.n) 
 55 {
 56     ++ct;
 57     cout << "copy constructor const called; number of objects: " << ct << endl;
 58     pc = new char[n];
 59     for (int i = 0; i < n; i++)
 60         pc[i] = f.pc[i];
 61     ShowObject();
 62 }
 63 
 64 Useless::~Useless()
 65 {
 66     cout << "destructor called; "; 
 67     cout << "deleted object:\n";
 68     ShowObject();
 69     delete [] pc;
 70     cout << "objects left: " << --ct << endl << endl;
 71 }
 72 
 73 Useless Useless::operator+(const Useless & f)const
 74 {
 75     cout << "Entering operator+()\n";
 76     Useless temp = Useless(n + f.n);
 77     for (int i = 0; i < n; i++)
 78         temp.pc[i] = pc[i];
 79     for (int i = n; i < temp.n; i++)
 80         temp.pc[i] = f.pc[i - n];
 81     cout << "temp object:\n";
 82     cout << "Leaving operator+()\n";
 83     return temp;
 84 }
 85 
 86 void Useless::ShowObject() const
 87 { 
 88     cout << "Number of elements: " << n;
 89     cout << " Data address: " << (void *) pc << endl << endl;
 90 }
 91 
 92 void Useless::ShowData() const
 93 {
 94     if (0 == n)
 95     {
 96         cout << "(object empty)";
 97     }
 98     else
 99     {
100         for (int i = 0; i < n; i++)
101             cout << pc[i];
102     }
103     cout << endl;
104 }
105 
106 // application
107 int main()
108 {
109     {
110         Useless one(10, 'x');
111         Useless two = one;          // calls copy constructor
112         Useless three(20, 'o');
113         Useless four(one + three);  // calls operator+(), copy constructor
114         cout << "object one: ";
115         one.ShowData();
116         cout << "object two: ";
117         two.ShowData();
118         cout << "object three: ";
119         three.ShowData();
120         cout << "object four: ";
121         four.ShowData();
122     }
123     cin.get();
124 }
125 
126 // out
127 /*
128 Useless(int k, char ch) constructor called; number of objects: 1
129 Number of elements: 10 Data address: 004A4910
130 
131 copy constructor const called; number of objects: 2
132 Number of elements: 10 Data address: 004A4958
133 
134 Useless(int k, char ch) constructor called; number of objects: 3
135 Number of elements: 20 Data address: 004A49A0
136 
137 Entering operator+()
138 Useless(int k) constructor called; number of objects: 4
139 Number of elements: 30 Data address: 004A49F0
140 
141 temp object:
142 Leaving operator+()
143 copy constructor const called; number of objects: 5
144 Number of elements: 30 Data address: 004A4C50
145 
146 destructor called; deleted object:
147 Number of elements: 30 Data address: 004A49F0
148 
149 objects left: 4
150 
151 object one: xxxxxxxxxx
152 object two: xxxxxxxxxx
153 object three: oooooooooooooooooooo
154 object four: xxxxxxxxxxoooooooooooooooooooo
155 destructor called; deleted object:
156 Number of elements: 30 Data address: 004A4C50
157 
158 objects left: 3
159 
160 destructor called; deleted object:
161 Number of elements: 20 Data address: 004A49A0
162 
163 objects left: 2
164 
165 destructor called; deleted object:
166 Number of elements: 10 Data address: 004A4958
167 
168 objects left: 1
169 
170 destructor called; deleted object:
171 Number of elements: 10 Data address: 004A4910
172 
173 objects left: 0
174 
175 */

运行结果分析如下:

从运算符+重载开始琢磨,如下:

 1 Useless Useless::operator+(const Useless & f)const
 2 {
 3     cout << "Entering operator+()\n";
 4     Useless temp = Useless(n + f.n);
 5     for (int i = 0; i < n; i++)
 6         temp.pc[i] = pc[i];
 7     for (int i = n; i < temp.n; i++)
 8         temp.pc[i] = f.pc[i - n];
 9     cout << "temp object:\n";
10     cout << "Leaving operator+()\n";
11     return temp;
12 }

运算符+重载实现中,创建了一个局部对象temp(注意与临时对象的区别),最终函数结束时返回该对象。

由于函数返回值类型,所以这里只能调用复制构造函数来创建对象four。如下:

1 Useless::Useless(const Useless & f) : n(f.n) 
2 {
3     ++ct;
4     cout << "copy constructor const called; number of objects: " << ct << endl;
5     pc = new char[n];
6     for (int i = 0; i < n; i++)
7         pc[i] = f.pc[i];
8     ShowObject();
9 }

因此复制构造函数的形参f直接指向该局部对象(即实参),通过另开辟空间、深拷贝该局部对象从而完成创建对象four的任务。

然后,析构掉该局部对象。关键这里白白析构掉这个局部对象申请的内存空间的确有点太奢侈了,尤其从时间方面权衡整个过程。

如果,有一种构造函数可让对象four的成员变量pc指针直接指向该局部对象已开辟的内存空间,而不需用再像复制构造函数那样又另开辟空间来创建对象four。

那么,这个过程将节省多少时间呢?这里要点是做了大量的无用功。即就是说,将临时对象的所有权直接交给对象four,不是更完美吗?

2.2 移动语义是什么?

请看如下示例,增加移动构造函数:

  1 // 例2:复制构造函数 与 移动构造函数
  2 // useless.cpp -- an otherwise useless class with move semantics
  3 #include <iostream>
  4 using namespace std;
  5 
  6 // interface
  7 class Useless
  8 {
  9 private:
 10     int n;          // number of elements
 11     char * pc;      // pointer to data
 12     static int ct;  // number of objects
 13     void ShowObject() const;
 14 
 15 public:
 16     Useless();
 17     explicit Useless(int k);
 18     Useless(int k, char ch);
 19     Useless(const Useless & f); // regular copy constructor
 20     Useless(Useless && f);      // move constructor
 21     Useless & operator=(const Useless & f); // copy assignment
 22     Useless & operator=(Useless && f); // move assignment
 23     ~Useless();
 24     Useless operator+(const Useless & f) const;
 25     void ShowData() const;
 26 };
 27 
 28 // implementation
 29 int Useless::ct = 0;
 30 
 31 Useless::Useless()
 32 {
 33     ++ct;
 34     n = 0;
 35     pc = nullptr;
 36     cout << "default constructor called; number of objects: " << ct << endl;
 37     ShowObject();
 38 }
 39 
 40 Useless::Useless(int k) : n(k)
 41 {
 42     ++ct; 
 43     cout << "Useless(int k) constructor called; number of objects: " << ct << endl;
 44     pc = new char[n];
 45     ShowObject();
 46 }
 47 
 48 Useless::Useless(int k, char ch) : n(k)
 49 {
 50     ++ct;
 51     cout << "Useless(int k, char ch) constructor called; number of objects: " << ct << endl;
 52     pc = new char[n];
 53     for (int i = 0; i < n; i++)
 54         pc[i] = ch;
 55     ShowObject();
 56 }
 57 
 58 Useless::Useless(const Useless & f) : n(f.n) 
 59 {
 60     ++ct;
 61     cout << "copy constructor const called; number of objects: " << ct << endl;
 62     pc = new char[n];
 63     for (int i = 0; i < n; i++)
 64         pc[i] = f.pc[i];
 65     ShowObject();
 66 }
 67 
 68 Useless::Useless(Useless && f) : n(f.n) 
 69 {
 70     ++ct;
 71     cout << "move constructor called; number of objects: " << ct << endl;
 72     pc = f.pc;       // steal address
 73     f.pc = nullptr;  // give old object nothing in return
 74     f.n = 0;
 75     ShowObject();
 76 }
 77 
 78 Useless & Useless::operator=(const Useless & f)
 79 {
 80     if (this == &f)
 81         return *this;
 82     delete []pc;
 83     n = f.n;
 84     pc = new char[n];
 85     for (int i = 0; i < n; ++i)
 86         pc[i] = f.pc[i];
 87     return *this;
 88 }
 89 
 90 Useless & Useless::operator=(Useless && f)
 91 {
 92     if (this == &f)
 93         return *this;
 94     delete []pc;
 95     n = f.n;
 96     pc = f.pc;
 97     f.n = 0;
 98     f.pc = nullptr;
 99     return *this;
100 }
101 
102 Useless::~Useless()
103 {
104     cout << "destructor called; "; 
105     cout << "deleted object:\n";
106     ShowObject();
107     delete [] pc;
108     cout << "objects left: " << --ct << endl << endl;
109 }
110 
111 Useless Useless::operator+(const Useless & f)const
112 {
113     cout << "Entering operator+()\n";
114     Useless temp = Useless(n + f.n);
115     for (int i = 0; i < n; i++)
116         temp.pc[i] = pc[i];
117     for (int i = n; i < temp.n; i++)
118         temp.pc[i] = f.pc[i - n];
119     cout << "temp object:\n";
120     cout << "Leaving operator+()\n";
121     return temp;
122 }
123 
124 void Useless::ShowObject() const
125 { 
126     cout << "Number of elements: " << n;
127     cout << " Data address: " << (void *) pc << endl << endl;
128 }
129 
130 void Useless::ShowData() const
131 {
132     if (0 == n)
133     {
134         cout << "(object empty)";
135     }
136     else
137     {
138         for (int i = 0; i < n; i++)
139             cout << pc[i];
140     }
141     cout << endl;
142 }
143 
144 // application
145 int main()
146 {
147     {
148         Useless one(10, 'x');
149         Useless two = one;          // calls copy constructor
150         Useless three(20, 'o');
151         Useless four(one + three);  // calls operator+(), move constructor
152         cout << "object one: ";
153         one.ShowData();
154         cout << "object two: ";
155         two.ShowData();
156         cout << "object three: ";
157         three.ShowData();
158         cout << "object four: ";
159         four.ShowData();
160     }
161     cin.get();
162 }
163 
164 // out
165 /*
166 Useless(int k, char ch) constructor called; number of objects: 1
167 Number of elements: 10 Data address: 00224910
168 
169 copy constructor const called; number of objects: 2
170 Number of elements: 10 Data address: 00224958
171 
172 Useless(int k, char ch) constructor called; number of objects: 3
173 Number of elements: 20 Data address: 002249A0
174 
175 Entering operator+()
176 Useless(int k) constructor called; number of objects: 4
177 Number of elements: 30 Data address: 002249F0
178 
179 temp object:
180 Leaving operator+()
181 move constructor called; number of objects: 5
182 Number of elements: 30 Data address: 002249F0
183 
184 destructor called; deleted object:
185 Number of elements: 0 Data address: 00000000
186 
187 objects left: 4
188 
189 object one: xxxxxxxxxx
190 object two: xxxxxxxxxx
191 object three: oooooooooooooooooooo
192 object four: xxxxxxxxxxoooooooooooooooooooo
193 destructor called; deleted object:
194 Number of elements: 30 Data address: 002249F0
195 
196 objects left: 3
197 
198 destructor called; deleted object:
199 Number of elements: 20 Data address: 002249A0
200 
201 objects left: 2
202 
203 destructor called; deleted object:
204 Number of elements: 10 Data address: 00224958
205 
206 objects left: 1
207 
208 destructor called; deleted object:
209 Number of elements: 10 Data address: 00224910
210 
211 objects left: 0
212 
213 */

运行结果分析如下:

注意:上面分析运行结果图中,把temp称为局部变量,这次运行结果分析图中,把temp称为了局部对象。

实质上,变量和对象是一回事,只不过人们习惯上把用内置类型定义的东东称为变量,而把自定义类型定义的东东称为对象(与类相匹配)。

通过实践,我们发现:移动构造函数可以完美实现夙愿。

从运行结果看到内存地址002249F0出现了三次:首次出现在构建局部对象时,二次出现在调用移动构造函数创建对象four时,第三次出现在析构对象four时。

很显然,对象four的创建通过调用移动构造函数而没有再另申请内存空间,仅仅只是改变了局部对象实质内容的所有权而已。

而观察局部对象析构时的打印信息:其指针成员值为空。目的为了防止对同一块内存多次delete引起程序的致命性错误。因为对空指针多次delete没有任何意义。

如何理解程序中就多次delete同一块内存了呢?

第一次,析构局部对象temp,其实释放的正是内存002249F0,只不过在析构之前移动构造函数将其已移动完成并置为空。

第二次,析构对象four,释放的也是内存002249F0。(还没有看懂的话,可对比上面只有复制构造函数的程序运行结果再琢磨琢磨其中的套路。)

总结:这个过程类似于在计算机中移动文件的情形:实际文件还留在原来的地方,只不过仅仅修改记录而已,这种方法被称为移动语义。

实质上,移动语义即不移动原始数据,仅仅修改了记录(指针指向)而已。

2.3 C++11如何支持移动语义?

要实现移动语义,需要采取某种方式,让编译器知道什么时候需要复制,什么时候不需要,而这正是右值引用发挥作用的地方。

可定义两个构造函数,其中一个是常规复制构造函数,它使用const左值引用作为参数,这个引用关联到左值实参;

另一个是移动构造函数,它使用右值引用作为参数,该引用关联到右值实参。复制构造函数可执行深复制,而移动构造函数只调整记录。

在将所有权转移给新对象的过程中,移动构造函数可能修改其实参,这意味着右值引用参数不应用const修饰。

虽然使用右值引用可支持移动语义,但这并不会神奇的发生。要让移动语义发生,需要两个步骤:

1. 右值引用让编译器知道何时可使用移动语义。

比如,上面的示例中对象one是左值,与左值引用匹配,而表达式one + three是右值,与右值引用匹配。

2. 为自定义类编写移动构造函数,使其具备所需的行为能力。

【3】移动语义之移动赋值运算符

适用于构造函数的移动语义考虑也适用于赋值运算符。

示例1. 只有赋值运算符函数

  1 // 只有复制赋值运算符
  2 #include <iostream>
  3 using namespace std;
  4 
  5 // interface
  6 class Useless
  7 {
  8 private:
  9     int n;          // number of elements
 10     char * pc;      // pointer to data
 11     static int ct;  // number of objects
 12     void ShowObject() const;
 13 
 14 public:
 15     Useless();
 16     explicit Useless(int k);
 17     Useless(int k, char ch);
 18     Useless(const Useless & f); // regular copy constructor
 19     Useless(Useless && f);      // move constructor
 20     Useless & operator=(const Useless & f); // copy assignment
 21     ~Useless();
 22     Useless operator+(const Useless & f)const;
 23     void ShowData() const;
 24 };
 25 
 26 // implementation
 27 int Useless::ct = 0;
 28 
 29 Useless::Useless()
 30 {
 31     ++ct;
 32     n = 0;
 33     pc = nullptr;
 34     cout << "default constructor called; number of objects: " << ct << endl;
 35     ShowObject();
 36 }
 37 
 38 Useless::Useless(int k) : n(k)
 39 {
 40     ++ct; 
 41     cout << "Useless(int k) constructor called; number of objects: " << ct << endl;
 42     pc = new char[n];
 43     ShowObject();
 44 }
 45 
 46 Useless::Useless(int k, char ch) : n(k)
 47 {
 48     ++ct;
 49     cout << "Useless(int k, char ch) constructor called; number of objects: " << ct << endl;
 50     pc = new char[n];
 51     for (int i = 0; i < n; i++)
 52         pc[i] = ch;
 53     ShowObject();
 54 }
 55 
 56 Useless::Useless(const Useless & f) : n(f.n) 
 57 {
 58     ++ct;
 59     cout << "copy const called; number of objects: " << ct << endl;
 60     pc = new char[n];
 61     for (int i = 0; i < n; i++)
 62         pc[i] = f.pc[i];
 63     ShowObject();
 64 }
 65 
 66 Useless::Useless(Useless && f) : n(f.n) 
 67 {
 68     ++ct;
 69     cout << "move constructor called; number of objects: " << ct << endl;
 70     pc = f.pc;       // steal address
 71     f.pc = nullptr;  // give old object nothing in return
 72     f.n = 0;
 73     ShowObject();
 74 }
 75 
 76 Useless & Useless::operator=(const Useless & f)
 77 {
 78     cout << "copy assignment operator= called;\n";
 79     if (this == &f)
 80         return *this;
 81     delete []pc;
 82     n = f.n;
 83     pc = new char[n];
 84     for (int i = 0; i < n; ++i)
 85         pc[i] = f.pc[i];
 86     return *this;
 87 }
 88 
 89 Useless::~Useless()
 90 {
 91     cout << "destructor called; deleted object: "; 
 92     ShowObject();
 93     delete [] pc;
 94     cout << "objects left: " << --ct << endl;
 95 }
 96 
 97 Useless Useless::operator+(const Useless & f)const
 98 {
 99     cout << "Entering operator+()\n";
100     Useless temp = Useless(n + f.n);
101     for (int i = 0; i < n; i++)
102         temp.pc[i] = pc[i];
103     for (int i = n; i < temp.n; i++)
104         temp.pc[i] = f.pc[i - n];
105     cout << "temp object:\n";
106     cout << "Leaving operator+()\n";
107     return temp;
108 }
109 
110 void Useless::ShowObject() const
111 { 
112     cout << "Number of elements: " << n;
113     cout << " Data address: " << (void *) pc << endl;
114 }
115 
116 void Useless::ShowData() const
117 {
118     if (0 == n)
119     {
120         cout << "(object empty)";
121     }
122     else
123     {
124         for (int i = 0; i < n; i++)
125             cout << pc[i];
126     }
127     cout << endl;
128 }
129 
130 // application
131 int main()
132 {
133     {
134         Useless one(10, 'x');
135         Useless two = one + one;  // calls move constructor
136         cout << "Object one: ";
137         one.ShowData(); 
138         cout << "Object two: ";
139         two.ShowData();
140         Useless three, four;
141         cout << "three = one \n";
142         three = one;           // automatic copy assignment 
143         cout << "Now Object three = ";
144         three.ShowData();
145         cout << "And Object one = ";
146         one.ShowData();
147         cout << "four = one + two \n";
148         four = one + two;       // automatic move assignment
149         cout << "Now Object four = ";
150         four.ShowData();
151         cout << "four = move(one)\n";
152         four = std::move(one);   // forced move assignment
153         cout << "Now Object four = ";
154         four.ShowData();
155         cout << "And Object one = ";
156         one.ShowData();
157     }
158     cin.get();
159 }
160 
161 // run out
162 /*
163 Useless(int k, char ch) constructor called; number of objects: 1
164 Number of elements: 10 Data address: 00794910
165 Entering operator+()
166 Useless(int k) constructor called; number of objects: 2
167 Number of elements: 20 Data address: 00794958
168 temp object:
169 Leaving operator+()
170 move constructor called; number of objects: 3
171 Number of elements: 20 Data address: 00794958
172 destructor called; deleted object: Number of elements: 0 Data address: 00000000
173 objects left: 2
174 Object one: xxxxxxxxxx
175 Object two: xxxxxxxxxxxxxxxxxxxx
176 default constructor called; number of objects: 3
177 Number of elements: 0 Data address: 00000000
178 default constructor called; number of objects: 4
179 Number of elements: 0 Data address: 00000000
180 three = one
181 copy assignment operator= called;
182 Now Object three = xxxxxxxxxx
183 And Object one = xxxxxxxxxx
184 four = one + two
185 Entering operator+()
186 Useless(int k) constructor called; number of objects: 5
187 Number of elements: 30 Data address: 007949F0
188 temp object:
189 Leaving operator+()
190 move constructor called; number of objects: 6
191 Number of elements: 30 Data address: 007949F0
192 destructor called; deleted object: Number of elements: 0 Data address: 00000000
193 objects left: 5
194 copy assignment operator= called;
195 destructor called; deleted object: Number of elements: 30 Data address: 007949F0
196 
197 objects left: 4
198 Now Object four = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
199 four = move(one)
200 copy assignment operator= called;
201 Now Object four = xxxxxxxxxx
202 And Object one = xxxxxxxxxx
203 destructor called; deleted object: Number of elements: 10 Data address: 007949F0
204 
205 objects left: 3
206 destructor called; deleted object: Number of elements: 10 Data address: 007949A8
207 
208 objects left: 2
209 destructor called; deleted object: Number of elements: 20 Data address: 00794958
210 
211 objects left: 1
212 destructor called; deleted object: Number of elements: 10 Data address: 00794910
213 
214 objects left: 0
215 
216 */

由于没有写移动赋值运算符函数,以上执行结果不做分析(可对比下面的分析更充分的理解)。

示例2. 有赋值运算符函数,有移动赋值运算符函数

  1 // 有复制赋值运算符 且 有移动赋值运算符
  2 // useless.cpp -- an otherwise useless class with move semantics
  3 #include <iostream>
  4 using namespace std;
  5 
  6 // interface
  7 class Useless
  8 {
  9 private:
 10     int n;          // number of elements
 11     char * pc;      // pointer to data
 12     static int ct;  // number of objects
 13     void ShowObject() const;
 14 
 15 public:
 16     Useless();
 17     explicit Useless(int k);
 18     Useless(int k, char ch);
 19     Useless(const Useless & f); // regular copy constructor
 20     Useless(Useless && f);      // move constructor
 21     Useless & operator=(const Useless & f); // copy assignment
 22     Useless & operator=(Useless && f); // move assignment
 23     ~Useless();
 24     Useless operator+(const Useless & f)const;
 25     void ShowData() const;
 26 };
 27 
 28 // implementation
 29 int Useless::ct = 0;
 30 
 31 Useless::Useless()
 32 {
 33     ++ct;
 34     n = 0;
 35     pc = nullptr;
 36     cout << "default constructor called; number of objects: " << ct << endl;
 37     ShowObject();
 38 }
 39 
 40 Useless::Useless(int k) : n(k)
 41 {
 42     ++ct; 
 43     cout << "Useless(int k) constructor called; number of objects: " << ct << endl;
 44     pc = new char[n];
 45     ShowObject();
 46 }
 47 
 48 Useless::Useless(int k, char ch) : n(k)
 49 {
 50     ++ct;
 51     cout << "Useless(int k, char ch) constructor called; number of objects: " << ct << endl;
 52     pc = new char[n];
 53     for (int i = 0; i < n; i++)
 54         pc[i] = ch;
 55     ShowObject();
 56 }
 57 
 58 Useless::Useless(const Useless & f) : n(f.n) 
 59 {
 60     ++ct;
 61     cout << "copy const called; number of objects: " << ct << endl;
 62     pc = new char[n];
 63     for (int i = 0; i < n; i++)
 64         pc[i] = f.pc[i];
 65     ShowObject();
 66 }
 67 
 68 Useless::Useless(Useless && f) : n(f.n) 
 69 {
 70     ++ct;
 71     cout << "move constructor called; number of objects: " << ct << endl;
 72     pc = f.pc;       // steal address
 73     f.pc = nullptr;  // give old object nothing in return
 74     f.n = 0;
 75     ShowObject();
 76 }
 77 
 78 Useless & Useless::operator=(const Useless & f)
 79 {
 80     cout << "copy assignment operator= called;\n";
 81     if (this == &f)
 82         return *this;
 83     delete []pc;
 84     n = f.n;
 85     pc = new char[n];
 86     for (int i = 0; i < n; ++i)
 87         pc[i] = f.pc[i];
 88     return *this;
 89 }
 90 
 91 Useless & Useless::operator=(Useless && f)
 92 {
 93     cout << "move assignment operator= called;\n";
 94     if (this == &f)
 95         return *this;
 96     delete []pc;
 97     n = f.n;
 98     pc = f.pc;
 99     f.n = 0;
100     f.pc = nullptr;
101     return *this;
102 }
103 
104 Useless::~Useless()
105 {
106     cout << "destructor called; deleted object: "; 
107     ShowObject();
108     delete [] pc;
109     cout << "objects left: " << --ct << endl;
110 }
111 
112 Useless Useless::operator+(const Useless & f)const
113 {
114     cout << "Entering operator+()\n";
115     Useless temp = Useless(n + f.n);
116     for (int i = 0; i < n; i++)
117         temp.pc[i] = pc[i];
118     for (int i = n; i < temp.n; i++)
119         temp.pc[i] = f.pc[i - n];
120     cout << "temp object:\n";
121     cout << "Leaving operator+()\n";
122     return temp;
123 }
124 
125 void Useless::ShowObject() const
126 { 
127     cout << "Number of elements: " << n;
128     cout << " Data address: " << (void *) pc << endl;
129 }
130 
131 void Useless::ShowData() const
132 {
133     if (0 == n)
134     {
135         cout << "(object empty)";
136     }
137     else
138     {
139         for (int i = 0; i < n; i++)
140             cout << pc[i];
141     }
142     cout << endl;
143 }
144 
145 // application
146 int main()
147 {
148     {
149         Useless one(10, 'x');
150         Useless two = one + one;  // calls move constructor
151         cout << "Object one: ";
152         one.ShowData(); 
153         cout << "Object two: ";
154         two.ShowData();
155         Useless three, four;
156         cout << "three = one \n";
157         three = one;           // automatic copy assignment 
158         cout << "Now Object three = ";
159         three.ShowData();
160         cout << "And Object one = ";
161         one.ShowData();
162         cout << "four = one + two \n";
163         four = one + two;       // automatic move assignment
164         cout << "Now Object four = ";
165         four.ShowData();
166         cout << "four = move(one)\n";
167         four = std::move(one);   // forced move assignment
168         cout << "Now Object four = ";
169         four.ShowData();
170         cout << "And Object one = ";
171         one.ShowData();
172     }
173     cin.get();
174 }
175 /*
176 Useless(int k, char ch) constructor called; number of objects: 1
177 Number of elements: 10 Data address: 00204910
178 Entering operator+()
179 Useless(int k) constructor called; number of objects: 2
180 Number of elements: 20 Data address: 00204958
181 temp object:
182 Leaving operator+()
183 move constructor called; number of objects: 3
184 Number of elements: 20 Data address: 00204958
185 destructor called; deleted object: Number of elements: 0 Data address: 00000000
186 objects left: 2
187 Object one: xxxxxxxxxx
188 Object two: xxxxxxxxxxxxxxxxxxxx
189 default constructor called; number of objects: 3
190 Number of elements: 0 Data address: 00000000
191 default constructor called; number of objects: 4
192 Number of elements: 0 Data address: 00000000
193 three = one
194 copy assignment operator= called;
195 Now Object three = xxxxxxxxxx
196 And Object one = xxxxxxxxxx
197 four = one + two
198 Entering operator+()
199 Useless(int k) constructor called; number of objects: 5
200 Number of elements: 30 Data address: 002049F0
201 temp object:
202 Leaving operator+()
203 move constructor called; number of objects: 6
204 Number of elements: 30 Data address: 002049F0
205 destructor called; deleted object: Number of elements: 0 Data address: 00000000
206 objects left: 5
207 move assignment operator= called;
208 destructor called; deleted object: Number of elements: 0 Data address: 00000000
209 objects left: 4
210 Now Object four = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
211 four = move(one)
212 move assignment operator= called;
213 Now Object four = xxxxxxxxxx
214 And Object one = (object empty)
215 destructor called; deleted object: Number of elements: 10 Data address: 00204910
216 
217 objects left: 3
218 destructor called; deleted object: Number of elements: 10 Data address: 002049A8
219 
220 objects left: 2
221 destructor called; deleted object: Number of elements: 20 Data address: 00204958
222 
223 objects left: 1
224 destructor called; deleted object: Number of elements: 0 Data address: 00000000
225 objects left: 0
226 */

运作结果分析如下:

移动赋值运算符函数如下:

 1 Useless & Useless::operator=(Useless && f)
 2 {
 3     cout << "move assignment operator= called;\n";
 4     if (this == &f)
 5         return *this;
 6     delete []pc;
 7     n = f.n;
 8     pc = f.pc;
 9     f.n = 0;
10     f.pc = nullptr;
11     return *this;
12 }

移动赋值运算符删除目标对象中的原始数据,并将源对象的所有权转让给目标对象。不能让多个指针指向相同的数据(同上移动构造函数的原理)。

【4】强制移动

移动构造函数和移动赋值运算符使用右值。如果想要强制移动,即要让它们使用左值作为实参,可使用运算符static_cast<>将对象的类型强制转换为Useless &&。

但C++11提供了更简单的方式——使用头文件utility中声明的函数std::move()。如上示例中已使用。

通过例子比较可知,函数std::move()并非一定会导致移动操作。表达式std::move(one)是右值,因此上例赋值语句将调用其移动赋值运算符(前提条件是定义了移动赋值运算符)。

如果没有定义移动赋值运算符,编译器将使用复制赋值运算符。如果也没有定义复制赋值运算符,将根本不允许上例的赋值。

 

Good Good Study, Day Day Up.

顺序 选择 循环 总结

posted @ 2016-12-25 23:10  kaizenly  阅读(3646)  评论(0编辑  收藏  举报
打赏