左值引用、右值引用和移动语义
2016-12-25 23:10
656 查看
【1】左值引用和右值引用
左值引用(一般所谓的引用形式)使标识符关联到左值。
何为左值?左值是一个表示数据的表达式(如变量名、解除引用的指针)。
最初,左值可出现在赋值语句的左边,但修饰符const的出现使得可以声明这样的标识符(即不能给它赋值,但可获取其地址)。
总而言之,判断左值的唯一条件是程序可获取其地址(即可对其应用地址运算符)。
右值引用使标识符关联到右值。右值引用是使用&&表示的。右值即可出现在赋值表达式右边,但不能对其应用地址运算符的值(与左值相反)。
右值包括字面常量(C-风格字符串除外,因为它表示地址),比如x+y等表达式以及返回值的函数(条件是该函数返回的不是引用)。
举例如下:
通过示例可以发现:将右值关联到右值引用导致该右值被存储到特定的位置,并且可通过标识符获取该位置的地址。也就是说,虽然不可将地址运算符&应用于12,但可将其用于r1。将数据与特定的地址关联,使得可通过右值引用来访问该数据信息。
下面演示及验证右值引用的作用:
为啥需要右值引用呢?引入右值引用的主要目的之一是实现移动语义。
【2】移动语义之移动构造函数
2.1 为何需要移动语义?
请看如下示例代码(相信你我已经写过很多类似的),只有复制构造函数情况下:
运行结果分析如下:
![](https://images2015.cnblogs.com/blog/389111/201612/389111-20161226012829570-1214354542.png)
从运算符+重载开始琢磨,如下:
运算符+重载实现中,创建了一个局部对象temp(注意与临时对象的区别),最终函数结束时返回该对象。
由于函数返回值类型,所以这里只能调用复制构造函数来创建对象four。如下:
因此复制构造函数的形参f直接指向该局部对象(即实参),通过另开辟空间、深拷贝该局部对象从而完成创建对象four的任务。
然后,析构掉该局部对象。关键这里白白析构掉这个局部对象申请的内存空间的确有点太奢侈了,尤其从时间方面权衡整个过程。
如果,有一种构造函数可让对象four的成员变量pc指针直接指向该局部对象已开辟的内存空间,而不需用再像复制构造函数那样又另开辟空间来创建对象four。
那么,这个过程将节省多少时间呢?这里要点是做了大量的无用功。即就是说,将临时对象的所有权直接交给对象four,不是更完美吗?
2.2 移动语义是什么?
请看如下示例,增加移动构造函数:
运行结果分析如下:
![](https://images2015.cnblogs.com/blog/389111/201612/389111-20161226022210632-1510073893.png)
注意:上面分析运行结果图中,把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. 只有赋值运算符函数
由于没有写移动赋值运算符函数,以上执行结果不做分析(可对比下面的分析更充分的理解)。
示例2. 有赋值运算符函数,有移动赋值运算符函数
运作结果分析如下:
![](https://images2015.cnblogs.com/blog/389111/201612/389111-20161226124401304-739480404.png)
移动赋值运算符函数如下:
移动赋值运算符删除目标对象中的原始数据,并将源对象的所有权转让给目标对象。不能让多个指针指向相同的数据(同上移动构造函数的原理)。
【4】强制移动
移动构造函数和移动赋值运算符使用右值。如果想要强制移动,即要让它们使用左值作为实参,可使用运算符static_cast<>将对象的类型强制转换为Useless &&。
但C++11提供了更简单的方式——使用头文件utility中声明的函数std::move()。如上示例中已使用。
通过例子比较可知,函数std::move()并非一定会导致移动操作。表达式std::move(one)是右值,因此上例赋值语句将调用其移动赋值运算符(前提条件是定义了移动赋值运算符)。
如果没有定义移动赋值运算符,编译器将使用复制赋值运算符。如果也没有定义复制赋值运算符,将根本不允许上例的赋值。
Good Good Study, Day Day Up.
顺序 选择 循环 总结
左值引用(一般所谓的引用形式)使标识符关联到左值。
何为左值?左值是一个表示数据的表达式(如变量名、解除引用的指针)。
最初,左值可出现在赋值语句的左边,但修饰符const的出现使得可以声明这样的标识符(即不能给它赋值,但可获取其地址)。
int n = 10; int * pt = new int; int & rn = n; // 变量名 int & rt = *pt; // 解除引用的指针 const int b = 20; // 不可以给b赋值,但可获取其地址 // b = 21; // error const int * pb = &b; // ok const int & rb = b; // 不可以给rb赋值,但可获取其地址 // rb = n; // error const int * prb = &rb; // ok
总而言之,判断左值的唯一条件是程序可获取其地址(即可对其应用地址运算符)。
右值引用使标识符关联到右值。右值引用是使用&&表示的。右值即可出现在赋值表达式右边,但不能对其应用地址运算符的值(与左值相反)。
右值包括字面常量(C-风格字符串除外,因为它表示地址),比如x+y等表达式以及返回值的函数(条件是该函数返回的不是引用)。
举例如下:
int x = 10; int y = 11; int && r1 = 12; // 常量 int && r2 = x + y; // 表达式 double && r3 = std::sqrt(2.0); // 函数返回值
通过示例可以发现:将右值关联到右值引用导致该右值被存储到特定的位置,并且可通过标识符获取该位置的地址。也就是说,虽然不可将地址运算符&应用于12,但可将其用于r1。将数据与特定的地址关联,使得可通过右值引用来访问该数据信息。
下面演示及验证右值引用的作用:
#include <iostream> using namespace std; double f(double tf) { return tf/20; }; void main() { double tc = 10.5; double && rd1 = 100.01; double && rd2 = 1.8 * tc; double && rd3 = f(rd2); cout << "tc Value And Address: " << tc << " " << &tc << endl; cout << "rd1 Value And Address: " << rd1 << " " << &rd1 << endl; cout << "rd2 Value And Address: " << rd2 << " " << &rd2 << endl; cout << "rd3 Value And Address: " << rd3 << " " << &rd3 << endl; cin.get(); } // run out /* tc Value And Address: 10.5 003FFAE4 rd1 Value And Address: 100.01 003FFAC8 rd2 Value And Address: 18.9 003FFAAC rd3 Value And Address: 0.945 003FFA90 */
为啥需要右值引用呢?引入右值引用的主要目的之一是实现移动语义。
【2】移动语义之移动构造函数
2.1 为何需要移动语义?
请看如下示例代码(相信你我已经写过很多类似的),只有复制构造函数情况下:
// 例1:只有复制构造函数 #include <iostream> using namespace std; // interface class Useless { private: int n; // number of elements char * pc; // pointer to data static int ct; // number of objects void ShowObject() const; public: Useless(); explicit Useless(int k); Useless(int k, char ch); Useless(const Useless & f); // regular copy constructor ~Useless(); Useless operator+(const Useless & f)const; void ShowData() const; }; // implementation int Useless::ct = 0; Useless::Useless() { ++ct; n = 0; pc = nullptr; cout << "default constructor called; number of objects: " << ct << endl; ShowObject(); } Useless::Useless(int k) : n(k) { ++ct; cout << "Useless(int k) constructor called; number of objects: " << ct << endl; pc = new char ; ShowObject(); } Useless::Useless(int k, char ch) : n(k) { ++ct; cout << "Useless(int k, char ch) constructor called; number of objects: " << ct << endl; pc = new char ; for (int i = 0; i < n; i++) pc[i] = ch; ShowObject(); } Useless::Useless(const Useless & f) : n(f.n) { ++ct; cout << "copy constructor const called; number of objects: " << ct << endl; pc = new char ; for (int i = 0; i < n; i++) pc[i] = f.pc[i]; ShowObject(); } Useless::~Useless() { cout << "destructor called; "; cout << "deleted object:\n"; ShowObject(); delete [] pc; cout << "objects left: " << --ct << endl << endl; } Useless Useless::operator+(const Useless & f)const { cout << "Entering operator+()\n"; Useless temp = Useless(n + f.n); for (int i = 0; i < n; i++) temp.pc[i] = pc[i]; for (int i = n; i < temp.n; i++) temp.pc[i] = f.pc[i - n]; cout << "temp object:\n"; cout << "Leaving operator+()\n"; return temp; } void Useless::ShowObject() const { cout << "Number of elements: " << n; cout << " Data address: " << (void *) pc << endl << endl; } void Useless::ShowData() const { if (0 == n) { cout << "(object empty)"; } else { for (int i = 0; i < n; i++) cout << pc[i]; } cout << endl; } // application int main() { { Useless one(10, 'x'); Useless two = one; // calls copy constructor Useless three(20, 'o'); Useless four(one + three); // calls operator+(), copy constructor cout << "object one: "; one.ShowData(); cout << "object two: "; two.ShowData(); cout << "object three: "; three.ShowData(); cout << "object four: "; four.ShowData(); } cin.get(); } // out /* Useless(int k, char ch) constructor called; number of objects: 1 Number of elements: 10 Data address: 004A4910 copy constructor const called; number of objects: 2 Number of elements: 10 Data address: 004A4958 Useless(int k, char ch) constructor called; number of objects: 3 Number of elements: 20 Data address: 004A49A0 Entering operator+() Useless(int k) constructor called; number of objects: 4 Number of elements: 30 Data address: 004A49F0 temp object: Leaving operator+() copy constructor const called; number of objects: 5 Number of elements: 30 Data address: 004A4C50 destructor called; deleted object: Number of elements: 30 Data address: 004A49F0 objects left: 4 object one: xxxxxxxxxx object two: xxxxxxxxxx object three: oooooooooooooooooooo object four: xxxxxxxxxxoooooooooooooooooooo destructor called; deleted object: Number of elements: 30 Data address: 004A4C50 objects left: 3 destructor called; deleted object: Number of elements: 20 Data address: 004A49A0 objects left: 2 destructor called; deleted object: Number of elements: 10 Data address: 004A4958 objects left: 1 destructor called; deleted object: Number of elements: 10 Data address: 004A4910 objects left: 0 */
运行结果分析如下:
![](https://images2015.cnblogs.com/blog/389111/201612/389111-20161226012829570-1214354542.png)
从运算符+重载开始琢磨,如下:
Useless Useless::operator+(const Useless & f)const { cout << "Entering operator+()\n"; Useless temp = Useless(n + f.n); for (int i = 0; i < n; i++) temp.pc[i] = pc[i]; for (int i = n; i < temp.n; i++) temp.pc[i] = f.pc[i - n]; cout << "temp object:\n"; cout << "Leaving operator+()\n"; return temp; }
运算符+重载实现中,创建了一个局部对象temp(注意与临时对象的区别),最终函数结束时返回该对象。
由于函数返回值类型,所以这里只能调用复制构造函数来创建对象four。如下:
Useless::Useless(const Useless & f) : n(f.n) { ++ct; cout << "copy constructor const called; number of objects: " << ct << endl; pc = new char ; for (int i = 0; i < n; i++) pc[i] = f.pc[i]; ShowObject(); }
因此复制构造函数的形参f直接指向该局部对象(即实参),通过另开辟空间、深拷贝该局部对象从而完成创建对象four的任务。
然后,析构掉该局部对象。关键这里白白析构掉这个局部对象申请的内存空间的确有点太奢侈了,尤其从时间方面权衡整个过程。
如果,有一种构造函数可让对象four的成员变量pc指针直接指向该局部对象已开辟的内存空间,而不需用再像复制构造函数那样又另开辟空间来创建对象four。
那么,这个过程将节省多少时间呢?这里要点是做了大量的无用功。即就是说,将临时对象的所有权直接交给对象four,不是更完美吗?
2.2 移动语义是什么?
请看如下示例,增加移动构造函数:
// 例2:复制构造函数 与 移动构造函数 // useless.cpp -- an otherwise useless class with move semantics #include <iostream> using namespace std; // interface class Useless { private: int n; // number of elements char * pc; // pointer to data static int ct; // number of objects void ShowObject() const; public: Useless(); explicit Useless(int k); Useless(int k, char ch); Useless(const Useless & f); // regular copy constructor Useless(Useless && f); // move constructor Useless & operator=(const Useless & f); // copy assignment Useless & operator=(Useless && f); // move assignment ~Useless(); Useless operator+(const Useless & f) const; void ShowData() const; }; // implementation int Useless::ct = 0; Useless::Useless() { ++ct; n = 0; pc = nullptr; cout << "default constructor called; number of objects: " << ct << endl; ShowObject(); } Useless::Useless(int k) : n(k) { ++ct; cout << "Useless(int k) constructor called; number of objects: " << ct << endl; pc = new char ; ShowObject(); } Useless::Useless(int k, char ch) : n(k) { ++ct; cout << "Useless(int k, char ch) constructor called; number of objects: " << ct << endl; pc = new char ; for (int i = 0; i < n; i++) pc[i] = ch; ShowObject(); } Useless::Useless(const Useless & f) : n(f.n) { ++ct; cout << "copy constructor const called; number of objects: " << ct << endl; pc = new char ; for (int i = 0; i < n; i++) pc[i] = f.pc[i]; ShowObject(); } Useless::Useless(Useless && f) : n(f.n) { ++ct; cout << "move constructor called; number of objects: " << ct << endl; pc = f.pc; // steal address f.pc = nullptr; // give old object nothing in return f.n = 0; ShowObject(); } Useless & Useless::operator=(const Useless & f) { if (this == &f) return *this; delete []pc; n = f.n; pc = new char ; for (int i = 0; i < n; ++i) pc[i] = f.pc[i]; return *this; } Useless & Useless::operator=(Useless && f) { if (this == &f) return *this; delete []pc; n = f.n; pc = f.pc; f.n = 0; f.pc = nullptr; return *this; } Useless::~Useless() { cout << "destructor called; "; cout << "deleted object:\n"; ShowObject(); delete [] pc; cout << "objects left: " << --ct << endl << endl; } Useless Useless::operator+(const Useless & f)const { cout << "Entering operator+()\n"; Useless temp = Useless(n + f.n); for (int i = 0; i < n; i++) temp.pc[i] = pc[i]; for (int i = n; i < temp.n; i++) temp.pc[i] = f.pc[i - n]; cout << "temp object:\n"; cout << "Leaving operator+()\n"; return temp; } void Useless::ShowObject() const { cout << "Number of elements: " << n; cout << " Data address: " << (void *) pc << endl << endl; } void Useless::ShowData() const { if (0 == n) { cout << "(object empty)"; } else { for (int i = 0; i < n; i++) cout << pc[i]; } cout << endl; } // application int main() { { Useless one(10, 'x'); Useless two = one; // calls copy constructor Useless three(20, 'o'); Useless four(one + three); // calls operator+(), move constructor cout << "object one: "; one.ShowData(); cout << "object two: "; two.ShowData(); cout << "object three: "; three.ShowData(); cout << "object four: "; four.ShowData(); } cin.get(); } // out /* Useless(int k, char ch) constructor called; number of objects: 1 Number of elements: 10 Data address: 00224910 copy constructor const called; number of objects: 2 Number of elements: 10 Data address: 00224958 Useless(int k, char ch) constructor called; number of objects: 3 Number of elements: 20 Data address: 002249A0 Entering operator+() Useless(int k) constructor called; number of objects: 4 Number of elements: 30 Data address: 002249F0 temp object: Leaving operator+() move constructor called; number of objects: 5 Number of elements: 30 Data address: 002249F0 destructor called; deleted object: Number of elements: 0 Data address: 00000000 objects left: 4 object one: xxxxxxxxxx object two: xxxxxxxxxx object three: oooooooooooooooooooo object four: xxxxxxxxxxoooooooooooooooooooo destructor called; deleted object: Number of elements: 30 Data address: 002249F0 objects left: 3 destructor called; deleted object: Number of elements: 20 Data address: 002249A0 objects left: 2 destructor called; deleted object: Number of elements: 10 Data address: 00224958 objects left: 1 destructor called; deleted object: Number of elements: 10 Data address: 00224910 objects left: 0 */
运行结果分析如下:
![](https://images2015.cnblogs.com/blog/389111/201612/389111-20161226022210632-1510073893.png)
注意:上面分析运行结果图中,把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. 只有赋值运算符函数
// 只有复制赋值运算符 #include <iostream> using namespace std; // interface class Useless { private: int n; // number of elements char * pc; // pointer to data static int ct; // number of objects void ShowObject() const; public: Useless(); explicit Useless(int k); Useless(int k, char ch); Useless(const Useless & f); // regular copy constructor Useless(Useless && f); // move constructor Useless & operator=(const Useless & f); // copy assignment ~Useless(); Useless operator+(const Useless & f)const; void ShowData() const; }; // implementation int Useless::ct = 0; Useless::Useless() { ++ct; n = 0; pc = nullptr; cout << "default constructor called; number of objects: " << ct << endl; ShowObject(); } Useless::Useless(int k) : n(k) { ++ct; cout << "Useless(int k) constructor called; number of objects: " << ct << endl; pc = new char ; ShowObject(); } Useless::Useless(int k, char ch) : n(k) { ++ct; cout << "Useless(int k, char ch) constructor called; number of objects: " << ct << endl; pc = new char ; for (int i = 0; i < n; i++) pc[i] = ch; ShowObject(); } Useless::Useless(const Useless & f) : n(f.n) { ++ct; cout << "copy const called; number of objects: " << ct << endl; pc = new char ; for (int i = 0; i < n; i++) pc[i] = f.pc[i]; ShowObject(); } Useless::Useless(Useless && f) : n(f.n) { ++ct; cout << "move constructor called; number of objects: " << ct << endl; pc = f.pc; // steal address f.pc = nullptr; // give old object nothing in return f.n = 0; ShowObject(); } Useless & Useless::operator=(const Useless & f) { cout << "copy assignment operator= called;\n"; if (this == &f) return *this; delete []pc; n = f.n; pc = new char ; for (int i = 0; i < n; ++i) pc[i] = f.pc[i]; return *this; } Useless::~Useless() { cout << "destructor called; deleted object: "; ShowObject(); delete [] pc; cout << "objects left: " << --ct << endl; } Useless Useless::operator+(const Useless & f)const { cout << "Entering operator+()\n"; Useless temp = Useless(n + f.n); for (int i = 0; i < n; i++) temp.pc[i] = pc[i]; for (int i = n; i < temp.n; i++) temp.pc[i] = f.pc[i - n]; cout << "temp object:\n"; cout << "Leaving operator+()\n"; return temp; } void Useless::ShowObject() const { cout << "Number of elements: " << n; cout << " Data address: " << (void *) pc << endl; } void Useless::ShowData() const { if (0 == n) { cout << "(object empty)"; } else { for (int i = 0; i < n; i++) cout << pc[i]; } cout << endl; } // application int main() { { Useless one(10, 'x'); Useless two = one + one; // calls move constructor cout << "Object one: "; one.ShowData(); cout << "Object two: "; two.ShowData(); Useless three, four; cout << "three = one \n"; three = one; // automatic copy assignment cout << "Now Object three = "; three.ShowData(); cout << "And Object one = "; one.ShowData(); cout << "four = one + two \n"; four = one + two; // automatic move assignment cout << "Now Object four = "; four.ShowData(); cout << "four = move(one)\n"; four = std::move(one); // forced move assignment cout << "Now Object four = "; four.ShowData(); cout << "And Object one = "; one.ShowData(); } cin.get(); } // run out /* Useless(int k, char ch) constructor called; number of objects: 1 Number of elements: 10 Data address: 00794910 Entering operator+() Useless(int k) constructor called; number of objects: 2 Number of elements: 20 Data address: 00794958 temp object: Leaving operator+() move constructor called; number of objects: 3 Number of elements: 20 Data address: 00794958 destructor called; deleted object: Number of elements: 0 Data address: 00000000 objects left: 2 Object one: xxxxxxxxxx Object two: xxxxxxxxxxxxxxxxxxxx default constructor called; number of objects: 3 Number of elements: 0 Data address: 00000000 default constructor called; number of objects: 4 Number of elements: 0 Data address: 00000000 three = one copy assignment operator= called; Now Object three = xxxxxxxxxx And Object one = xxxxxxxxxx four = one + two Entering operator+() Useless(int k) constructor called; number of objects: 5 Number of elements: 30 Data address: 007949F0 temp object: Leaving operator+() move constructor called; number of objects: 6 Number of elements: 30 Data address: 007949F0 destructor called; deleted object: Number of elements: 0 Data address: 00000000 objects left: 5 copy assignment operator= called; destructor called; deleted object: Number of elements: 30 Data address: 007949F0 objects left: 4 Now Object four = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx four = move(one) copy assignment operator= called; Now Object four = xxxxxxxxxx And Object one = xxxxxxxxxx destructor called; deleted object: Number of elements: 10 Data address: 007949F0 objects left: 3 destructor called; deleted object: Number of elements: 10 Data address: 007949A8 objects left: 2 destructor called; deleted object: Number of elements: 20 Data address: 00794958 objects left: 1 destructor called; deleted object: Number of elements: 10 Data address: 00794910 objects left: 0 */
由于没有写移动赋值运算符函数,以上执行结果不做分析(可对比下面的分析更充分的理解)。
示例2. 有赋值运算符函数,有移动赋值运算符函数
// 有复制赋值运算符 且 有移动赋值运算符 // useless.cpp -- an otherwise useless class with move semantics #include <iostream> using namespace std; // interface class Useless { private: int n; // number of elements char * pc; // pointer to data static int ct; // number of objects void ShowObject() const; public: Useless(); explicit Useless(int k); Useless(int k, char ch); Useless(const Useless & f); // regular copy constructor Useless(Useless && f); // move constructor Useless & operator=(const Useless & f); // copy assignment Useless & operator=(Useless && f); // move assignment ~Useless(); Useless operator+(const Useless & f)const; void ShowData() const; }; // implementation int Useless::ct = 0; Useless::Useless() { ++ct; n = 0; pc = nullptr; cout << "default constructor called; number of objects: " << ct << endl; ShowObject(); } Useless::Useless(int k) : n(k) { ++ct; cout << "Useless(int k) constructor called; number of objects: " << ct << endl; pc = new char ; ShowObject(); } Useless::Useless(int k, char ch) : n(k) { ++ct; cout << "Useless(int k, char ch) constructor called; number of objects: " << ct << endl; pc = new char ; for (int i = 0; i < n; i++) pc[i] = ch; ShowObject(); } Useless::Useless(const Useless & f) : n(f.n) { ++ct; cout << "copy const called; number of objects: " << ct << endl; pc = new char ; for (int i = 0; i < n; i++) pc[i] = f.pc[i]; ShowObject(); } Useless::Useless(Useless && f) : n(f.n) { ++ct; cout << "move constructor called; number of objects: " << ct << endl; pc = f.pc; // steal address f.pc = nullptr; // give old object nothing in return f.n = 0; ShowObject(); } Useless & Useless::operator=(const Useless & f) { cout << "copy assignment operator= called;\n"; if (this == &f) return *this; delete []pc; n = f.n; pc = new char ; for (int i = 0; i < n; ++i) pc[i] = f.pc[i]; return *this; } Useless & Useless::operator=(Useless && f) { cout << "move assignment operator= called;\n"; if (this == &f) return *this; delete []pc; n = f.n; pc = f.pc; f.n = 0; f.pc = nullptr; return *this; } Useless::~Useless() { cout << "destructor called; deleted object: "; ShowObject(); delete [] pc; cout << "objects left: " << --ct << endl; } Useless Useless::operator+(const Useless & f)const { cout << "Entering operator+()\n"; Useless temp = Useless(n + f.n); for (int i = 0; i < n; i++) temp.pc[i] = pc[i]; for (int i = n; i < temp.n; i++) temp.pc[i] = f.pc[i - n]; cout << "temp object:\n"; cout << "Leaving operator+()\n"; return temp; } void Useless::ShowObject() const { cout << "Number of elements: " << n; cout << " Data address: " << (void *) pc << endl; } void Useless::ShowData() const { if (0 == n) { cout << "(object empty)"; } else { for (int i = 0; i < n; i++) cout << pc[i]; } cout << endl; } // application int main() { { Useless one(10, 'x'); Useless two = one + one; // calls move constructor cout << "Object one: "; one.ShowData(); cout << "Object two: "; two.ShowData(); Useless three, four; cout << "three = one \n"; three = one; // automatic copy assignment cout << "Now Object three = "; three.ShowData(); cout << "And Object one = "; one.ShowData(); cout << "four = one + two \n"; four = one + two; // automatic move assignment cout << "Now Object four = "; four.ShowData(); cout << "four = move(one)\n"; four = std::move(one); // forced move assignment cout << "Now Object four = "; four.ShowData(); cout << "And Object one = "; one.ShowData(); } cin.get(); } /* Useless(int k, char ch) constructor called; number of objects: 1 Number of elements: 10 Data address: 00204910 Entering operator+() Useless(int k) constructor called; number of objects: 2 Number of elements: 20 Data address: 00204958 temp object: Leaving operator+() move constructor called; number of objects: 3 Number of elements: 20 Data address: 00204958 destructor called; deleted object: Number of elements: 0 Data address: 00000000 objects left: 2 Object one: xxxxxxxxxx Object two: xxxxxxxxxxxxxxxxxxxx default constructor called; number of objects: 3 Number of elements: 0 Data address: 00000000 default constructor called; number of objects: 4 Number of elements: 0 Data address: 00000000 three = one copy assignment operator= called; Now Object three = xxxxxxxxxx And Object one = xxxxxxxxxx four = one + two Entering operator+() Useless(int k) constructor called; number of objects: 5 Number of elements: 30 Data address: 002049F0 temp object: Leaving operator+() move constructor called; number of objects: 6 Number of elements: 30 Data address: 002049F0 destructor called; deleted object: Number of elements: 0 Data address: 00000000 objects left: 5 move assignment operator= called; destructor called; deleted object: Number of elements: 0 Data address: 00000000 objects left: 4 Now Object four = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx four = move(one) move assignment operator= called; Now Object four = xxxxxxxxxx And Object one = (object empty) destructor called; deleted object: Number of elements: 10 Data address: 00204910 objects left: 3 destructor called; deleted object: Number of elements: 10 Data address: 002049A8 objects left: 2 destructor called; deleted object: Number of elements: 20 Data address: 00204958 objects left: 1 destructor called; deleted object: Number of elements: 0 Data address: 00000000 objects left: 0 */
运作结果分析如下:
![](https://images2015.cnblogs.com/blog/389111/201612/389111-20161226124401304-739480404.png)
移动赋值运算符函数如下:
Useless & Useless::operator=(Useless && f) { cout << "move assignment operator= called;\n"; if (this == &f) return *this; delete []pc; n = f.n; pc = f.pc; f.n = 0; f.pc = nullptr; return *this; }
移动赋值运算符删除目标对象中的原始数据,并将源对象的所有权转让给目标对象。不能让多个指针指向相同的数据(同上移动构造函数的原理)。
【4】强制移动
移动构造函数和移动赋值运算符使用右值。如果想要强制移动,即要让它们使用左值作为实参,可使用运算符static_cast<>将对象的类型强制转换为Useless &&。
但C++11提供了更简单的方式——使用头文件utility中声明的函数std::move()。如上示例中已使用。
通过例子比较可知,函数std::move()并非一定会导致移动操作。表达式std::move(one)是右值,因此上例赋值语句将调用其移动赋值运算符(前提条件是定义了移动赋值运算符)。
如果没有定义移动赋值运算符,编译器将使用复制赋值运算符。如果也没有定义复制赋值运算符,将根本不允许上例的赋值。
Good Good Study, Day Day Up.
顺序 选择 循环 总结
相关文章推荐
- C++ 0x 之左值与右值、右值引用、移动语义、传导模板(转载)
- c++ 11 移动语义、std::move 左值、右值、将亡值、纯右值、右值引用
- C++ 0x 之左值与右值、右值引用、移动语义、传导模板
- C++ 0x 之左值与右值、右值引用、移动语义、传导模板
- C++ 0x 之左值与右值、右值引用、移动语义、传导模板
- c++ 11 移动语义、std::move 左值、右值、将亡值、纯右值、右值引用
- 左值引用,右值引用以及移动语义
- C++ move semantics and rvalue reference 移动语义与右值引用
- 杂货边角(17):C++11的右值引用和移动语义
- C++11线程指南(4)--右值引用与移动语义
- C++11中右值引用和移动语义
- C++11右值引用:移动语义和完美转发
- C++11新特性:移动语义和右值引用
- C++11移动语义探讨——从临时对象到右值引用
- C++11之右值引用(二):右值引用与移动语义
- C++的杂七杂八:我家的返回值才不可能这么傲娇(右值引用和移动语义)
- 第15课 右值引用(2)_std::move和移动语义
- 右值引用和移动语义再思考
- c++ 11学习笔记--右值引用和移动构造语义
- 详解C++11中的右值引用与移动语义