C++11新特性:右值引用和转移构造函数
问题背景
- #include <iostream>
- using namespace std;
- vector<int> doubleValues (const vector<int>& v)
- {
- vector<int> new_values( v.size() );
- for (auto itr = new_values.begin(), end_itr = new_values.end(); itr != end_itr; ++itr )
- {
- new_values.push_back( 2 * *itr );
- }
- return new_values;
- }
- int main()
- {
- vector<int> v;
- for ( int i = 0; i < 100; i++ )
- {
- v.push_back( i );
- }
- v = doubleValues( v );
- }
先来分析一下上述代码的运行过程。
- vector<int> v;
- for ( int i = 0; i < 100; i++ )
- {
- v.push_back( i );
- }
以上5行语句在栈上新建了一个vector的实例,并在里面放了100个数。
- v = doubleValues( v )
这条语句调用函数doubleValues,函数的参数类型的const reference,常量引用,那么在实参形参结合的时候并不会将v复制一份,而是直接传递引用。所以在函数体内部使用的v就是刚才创建的那个vector的实例。
但是
- vector<int> new_values( v.size() );
这条语句新建了一个vector的实例new_values,并且复制了v的所有内容。但这是合理的,因为我们这是要将一个vector中所有的值翻倍,所以我们不应该改变原有的vector的内容。
- v = doubleValues( v );
函数执行完之后,new_values中放了翻倍之后的数值,作为函数的返回值返回。但是注意,这个时候doubleValue(v)的调用已经结束。开始执行 = 的语义。
赋值的过程实际上是将返回的vector<int>复制一份放入新的内存空间,然后改变v的地址,让v指向这篇内存空间。总的来说,我们刚才新建的那个vector又被复制了一遍。
但我们其实希望v能直接得到函数中复制好的那个vector。在C++11之前,我们只能通过传递指针来实现这个目的。但是指针用多了非常不爽。我们希望有更简单的方法。这就是我们为什么要引入右值引用和转移构造函数的原因。
左值和右值
- int a;
- a = 1; // here, a is an lvalue
上述的a就是一个左值。
- int x;
- int& getRef ()
- {
- return x;
- }
- getRef() = 4;
以上就是函数返回值做左值的例子。
- int x;
- int getVal ()
- {
- return x;
- }
- getVal();
这里getVal()得到的就是临时的一个值,没法对它进行赋值。
下面的语句就是错的。
- getVal() = 1;//compilation error
所以右值只能够用来给其他的左值赋值。
右值引用
在C++11中,你可以使用const的左值引用来绑定一个右值,比如说:
- const int& val = getVal();//right
- int& val = getVal();//error
因为左值引用并不是左值,并没有建立一片稳定的内存空间,所以如果不是const的话你就可以对它的内容进行修改,而右值又不能进行赋值,所以就会出错。因此只能用const的左值引用来绑定一个右值。
- const string&& name = getName(); // ok
- string&& name = getName(); // also ok
有了这个功能,我们就可以对原来的左值引用的函数进行重载,重载的函数参数使用右值引用。比如下面这个例子:
- printReference (const String& str)
- {
- cout << str;
- }
- printReference (String&& str)
- {
- cout << str;
- }
可以这么调用它。
- string me( "alex" );
- printReference( me ); // 调用第一函数,参数为左值常量引用
- printReference( getName() ); 调用第二个函数,参数为右值引用。
好了,现在我们知道C++11可以进行显示的右值引用了。但是我们如果用它来解决一开始那个复制的问题呢?
转移构造函数和转移赋值运算符
- class ArrayWrapper
- {
- public:
- ArrayWrapper (int n)
- : _p_vals( new int[ n ] )
- , _size( n )
- {}
- // copy constructor
- ArrayWrapper (const ArrayWrapper& other)
- : _p_vals( new int[ other._size ] )
- , _size( other._size )
- {
- for ( int i = 0; i < _size; ++i )
- {
- _p_vals[ i ] = other._p_vals[ i ];
- }
- }
- ~ArrayWrapper ()
- {
- delete [] _p_vals;
- }
- private:
- int *_p_vals;
- int _size;
- };
我们可以看到,这个类的拷贝构造函数显示新建了一片内存空间,然后又对传进来的左值引用进行了复制。
- class ArrayWrapper
- {
- public:
- // default constructor produces a moderately sized array
- ArrayWrapper ()
- : _p_vals( new int[ 64 ] )
- , _size( 64 )
- {}
- ArrayWrapper (int n)
- : _p_vals( new int[ n ] )
- , _size( n )
- {}
- // move constructor
- ArrayWrapper (ArrayWrapper&& other)
- : _p_vals( other._p_vals )
- , _size( other._size )
- {
- other._p_vals = NULL;
- }
- // copy constructor
- ArrayWrapper (const ArrayWrapper& other)
- : _p_vals( new int[ other._size ] )
- , _size( other._size )
- {
- for ( int i = 0; i < _size; ++i )
- {
- _p_vals[ i ] = other._p_vals[ i ];
- }
- }
- ~ArrayWrapper ()
- {
- delete [] _p_vals;
- }
- private:
- int *_p_vals;
- int _size;
- };
第一个构造函数就是转移构造函数。它先将other的域复制给自己。尤其是将_p_vals的指针赋值给自己的指针,这个过程相当于int的复制,所以非常快。然后将other里面_p_vals指针置成NULL。这样做有什么用呢?
- ~ArrayWrapper ()
- {
- delete [] _p_vals;
- }
它会delete掉_p_vals的内存空间。但是如果调用析构函数的时候_p_vals指向的是NULL,那么就不会delte任何内存空间。
- ArrayWrapper *aw = new ArrayWrapper((new ArrayWrapper(5)));
其中
- (new ArrayWrapper(5)
获得的实例就是一个右值,我们不妨称为r,当整条语句执行结束的时候就会被销毁,执行析构函数。
- other._p_vals = NULL;
的话,虽然aw已经获得了r的_p_vals的内存空间,但是之后r就被销毁了,那么r._p_vals的那片内存也被释放了,aw中的_p_vals指向的就是一个不合法的内存空间。所以我们就要防止这片空间被销毁。
右值引用也是左值
- class MetaData
- {
- public:
- MetaData (int size, const std::string& name)
- : _name( name )
- , _size( size )
- {}
- // copy constructor
- MetaData (const MetaData& other)
- : _name( other._name )
- , _size( other._size )
- {}
- // move constructor
- MetaData (MetaData&& other)
- : _name( other._name )
- , _size( other._size )
- {}
- std::string getName () const { return _name; }
- int getSize () const { return _size; }
- private:
- std::string _name;
- int _size;
- };
那么ArrayWrapper类现在就变成这个样子
- class ArrayWrapper
- {
- public:
- // default constructor produces a moderately sized array
- ArrayWrapper ()
- : _p_vals( new int[ 64 ] )
- , _metadata( 64, "ArrayWrapper" )
- {}
- ArrayWrapper (int n)
- : _p_vals( new int[ n ] )
- , _metadata( n, "ArrayWrapper" )
- {}
- // move constructor
- ArrayWrapper (ArrayWrapper&& other)
- : _p_vals( other._p_vals )
- , _metadata( other._metadata )
- {
- other._p_vals = NULL;
- }
- // copy constructor
- ArrayWrapper (const ArrayWrapper& other)
- : _p_vals( new int[ other._metadata.getSize() ] )
- , _metadata( other._metadata )
- {
- for ( int i = 0; i < _metadata.getSize(); ++i )
- {
- _p_vals[ i ] = other._p_vals[ i ];
- }
- }
- ~ArrayWrapper ()
- {
- delete [] _p_vals;
- }
- private:
- int *_p_vals;
- MetaData _metadata;
- };
同样,我们使用了转移构造函数来避免代码的复制。但是这里的转移构造函数对吗?
- _metadata( other._metadata )
我们希望的是other._metadata是一个右值,然后就会调用MetaData类的转移构造函数来避免数据的复制。但是很可惜,右值引用是左值。
- std::move
这条语句可以将左值转换为右值
- // 转移构造函数
- ArrayWrapper (ArrayWrapper&& other)
- : _p_vals( other._p_vals )
- , _metadata( std::move( other._metadata ) )
- {
- other._p_vals = NULL;
- }
这样就可以避免_metadata域的复制了。
函数返回右值引用
- int x;
- int getInt ()
- {
- return x;
- }
- int && getRvalueInt ()
- {
- // notice that it's fine to move a primitive type--remember, std::move is just a cast
- return std::move( x );
- }
C++11新特性:右值引用和转移构造函数的更多相关文章
- 【转】C++11 标准新特性: 右值引用与转移语义
VS2013出来了,对于C++来说,最大的改变莫过于对于C++11新特性的支持,在网上搜了一下C++11的介绍,发现这篇文章非常不错,分享给大家同时自己作为存档. 原文地址:http://www.ib ...
- C++11 标准新特性: 右值引用与转移语义
文章出处:https://www.ibm.com/developerworks/cn/aix/library/1307_lisl_c11/ 新特性的目的 右值引用 (Rvalue Referene) ...
- C++ 新特性-右值引用
作为最重要的一项语言特性,右值引用(rvalue references)被引入到 C++0x中.我们可以通过操作符“&&”来声明一个右值引用,原先在C++中使用“&”操作符声明 ...
- c++右值引用和转移构造函数
int &&i = ; //i绑定到了右值1 int b = ; cout << i << endl; //输出1 i = b; cout << i ...
- [转载] C++11中的右值引用
C++11中的右值引用 May 18, 2015 移动构造函数 C++98中的左值和右值 C++11右值引用和移动语义 强制移动语义std::move() 右值引用和右值的关系 完美转发 引用折叠推导 ...
- C++11特性-右值引用
什么是左值,什么是右值 常见的误区有 = 左边的是左值,右边的是右值. 左值:具有存储性质的对象,即lvalue对象,是指要实际占用内存空间.有内存地址的那些实体对象,例如:变量(variables) ...
- C++ 11 中的右值引用
C++ 11 中的右值引用 右值引用的功能 首先,我并不介绍什么是右值引用,而是以一个例子里来介绍一下右值引用的功能: #include <iostream> #include &l ...
- C++11中的右值引用
原文出处:http://kuring.me/post/cpp11_right_reference May 18, 2015 移动构造函数 C++98中的左值和右值 C++11右值引用和移动语义 强制移 ...
- C++11 右值引用和转移语义
新特性的目的 右值引用 (Rvalue Referene) 是 C++ 新标准 (C++11, 11 代表 2011 年 ) 中引入的新特性 , 它实现了转移语义 (Move Sementics) 和 ...
随机推荐
- sql server 判断日期当前月有多少天
declare @tm datetime set @tm = CONVERT(datetime,'2013-3-12')declare @days intselect @days = case whe ...
- C# 数据结构--单链表
什么是单链表 这两天看到很多有关单链表的面试题,对单链表都不知道是啥的我.经过学习和整理来分享一下啥是单链表和单链表的一些基本使用方法.最后看些网上有关单链表的面试题代码实例. 啥是单链表? 单链表是 ...
- c#跨窗体调用操作
我知道的常用的有三种,以前记录的笔记: 1.通过构造函数实现 在form1的load事件中new form2时 在构造函数里添加一个参数 此参数就是form1类型的参数,同时记得在form2里重写构 ...
- Ubuntu gedit 折叠插件
Ubuntu Kylin 14.04 gedit - Version 3.10.4 (as same as all version of gedit 3.x ) Attention: this pl ...
- iOS 非ARC基本内存管理系列 -手把手教你ARC——iOS/Mac开发ARC入门和使用(转)
手把手教你ARC——iOS/Mac开发ARC入门和使用 Revolution of Objective-c 本文部分实例取自iOS 5 Toturail一书中关于ARC的教程和公开内容,仅用于技术交流 ...
- 6.JAVA_SE复习(集合)
集合 结构图: 总结: 1.集合中的元素都是对象(注意不是基本数据类型),基本数据类型要放入集合需要装箱. 2.set与list的主要区别在于set中不允许重复,而list(序列)中可以有重复对象. ...
- SMB/CIFS协议解析二
一.拷贝文件(远程-->本地) 1.SMB_COM_NT_CREATE_ANDX (0xa2) 打开文件,获取文件名,获得读取文件的 总长度. 2.SMB_COM_READ ...
- Traveller项目介绍
Traveller,翻译为旅行家,是我用来实践最佳web技术的项目,主题是一个给旅行爱好者提供旅行信息的网站. 目标是组合现最流行的web技术,实现符合中国用户使用习惯的网站. 相关网址 Git:ht ...
- Jquery 控件
1. Jeditable 2. Select2 3. superfish 4. Jquery file upload https://blueimp.github.io/jQuery-File-Upl ...
- 浏览器的visibilitychange 事件ie10以下不兼容
方法1, <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF- ...