&& 右值引用
来源:互联网 发布:淘宝库存不足 编辑:程序博客网 时间:2024/05/16 06:33
c++11中的新概念,主要解决了移动语义和完美转发
我们平常使用的引用都是指左值引用。
以下是我经常用到的手法:
void func( int& v);
void func( std::string& s);
下面是一个测试例子
// =======================================================================================// rvaluevoid func ( int& i ) { std::cout << "func(int& i) = " << i << std::endl;}void func ( std::string& s ) { std::cout << "func(std::string& s) = " << s << std::endl;}void rvalue () { std::cout << std::endl << std::endl << "-------------------------- rvalue test------------------------" << std::endl; int i = 123; std::string s = "hello"; func ( i ); // ok func ( s ); // ok func ( 1 ); // error : error C2665: “func”: 2 个重载中没有一个可以转换所有参数类型}// =======================================================================================
反正上面是出了问题,我么先了解一下基础知识在来回答上面的问题。
左值(lvalue)和右值(rvalue)是从c继承过来的概念,在c++11之后,新标准基于这两个概念新增了部分特征(右值引用,用来解决移动和转发语义)。
什么是左值、右值?
通俗的将:=左边的是左值,右边的是右值。
lvalue和rvalue的前缀怎么理解,left和right?好吧,l表示location,r表示read。
location表示可以在内存中寻址,可以被赋值,read表示可以直接知道值
什么是临时变量?
对于全局变量、局部变量,我们都清楚,那么什么是临时变量,什么时候出现。
怎么交换两个数?建一个临时变量和第一个交换,两个变量交换,临时变量和第二个交换。sorry,这只是我们业务逻辑上的临时变量,确切的说,只是我们创建的一个局部变量而已,那对于程序(或者说运行中的程序来说)什么才是临时变量?网上有位大神是这么解释的:编译期由编译器根据程序需要自动生成的,在运行期跑的时候是真实存在的,而主要发生在两个地方:函数传参发生类型转换时、函数返回值时。
比如说函数调用时,我们传一个字面量过去;或者函数返回一个字面量时;或是表达式中发生了类型转换时。
在c++中,临时对象不能作为左值,但可以作为常量引用const &
看下面的
++i = 3; // ok i++ = 3; // error C2106: “=”: 左操作数必须为左值
++i:i先自增1,++i之后还是指向i的对象
i++:先把i自增之后的值丢给一个临时变量,自己去玩泥巴去了,i++之后指向的是那个临时变量。如果放在单条语句中,这两个没啥区别。
好了,现在看看上面出问题的地方func(1); 这个函数需要的是一个左值,但是字面量丢过去,她也只能放在一个临时变量中,而这个临时变量不能作为一个左值存在,就出了问题。
简单解决一下:
void func ( int&& i ) { std::cout << "func(int&& i) = " << i << std::endl;}
或是用func ( const int& i )
好吧,这都不是今天要说的重点,重点是const int&& i;
&& 和 &一样都是引用,&&是新标准弄出来的,称为 右值引用。
无名右值引用
一般由static_cast < T&& >(t)转换操作转换而来
也可以用标准库提供的std::move()来将左值转换成右值引用
带名右值引用
T&& 这是一个左值,只不过她的类型是右值引用,只能绑定右值
额外说一句:如果类型是T&& 且这个T类型无需推导即可确定,那么这个变量称为带名右值引用;如果这个T类型需要推导,那么这个变量称为转发型引用。
总结一下:
1、新标准为c++带来了一个新的左值类型:带名右值引用;带来了一个新的右值类型:无名右值引用
2、左值引用(就是以前的引用)可以绑定左值,也可以绑定右值;右值引用只能绑定右值(新型右值类型(右值引用)、传统右值类型(临时对象))。
A a;
A&& b = static_cast< A&&>(a);
A&& c = std::move(a);
创建两个无名引用,用来初始化两个右值引用对象
A& d = a;
A& e = b;
左值引用可以绑定左值
const A& f = c;
const A& g = A();
const引用(若无特殊说明,引用二字都是指传统的左值引用)还可以绑定临时变量
A&& h = A();
右值引用也可以绑定临时变量
如果重载存在const引用和右值引用,新标准规定右值引用参数绑定的函数优先级高于const引用
// =======================================================================================// rvaluevoid func (const int& i ) { std::cout << "func(int& i) = " << i << std::endl;}void func ( int&& i ) { std::cout << "func(int&& i) = " << i << std::endl;}void func ( std::string& s ) { std::cout << "func(std::string& s) = " << s << std::endl;}void rvalue () { std::cout << std::endl << std::endl << "-------------------------- rvalue test------------------------" << std::endl; int i = 123; std::string s = "hello"; func ( i ); // ok func ( s ); // ok// ++i = 3; // ok// i++ = 3; // error C2106: “=”: 左操作数必须为左值 func ( 1 ); // 传过去是一个右值,根据新标准规定调用void func ( int&& i ) int&& a = std::move ( i ); func ( a ); // 左值 别忘了a是左值,类型是右值引用 func ( static_cast<int&&>( i ) ); // 右值}// =======================================================================================
结果如下:
————————– rvalue test————————
func(int& i) = 123
func(std::string& s) = hello
func(int&& i) = 1 // 虽然两个重载都满足,但是新标准规定右值引用绑定的优先级高于const左值引用
func(int& i) = 123
func(int&& i) = 123
接下来看看右值引用在移动构造和移动赋值中的应用
什么是移动语义?
可以对比拷贝语义对着看
目的:
移动:接管另一个对象所有外部资源的所有权
拷贝:复制另一个对象所有外部资源,并接管新生资源的所有权
过程:
移动:
释放this资源
将this对象的指针或句柄指向that对象拥有的资源
将that对象中指向资源的指针或句柄置空
拷贝:
释放this资源
拷贝that拥有的资源
将this对象的指针或句柄指向新生资源
如果that对象时临时对象(右值),在拷贝完之后,that对象会被释放
move constructor和move assignment
copy constructor和copy assignment的区别:
构造函数相比赋值函数来说,因为this对象为空,所以不需要进行this资源释放(第一步)
如果that是左值,移动和拷贝不能替换;如果是右值,此时移动可以代替拷贝,好处是完成相同的目标的同时,少用了一次拷贝和释放。
下面是网上找的一个例子,
#include <iostream>class MemoryBlock{public: // 构造器(初始化资源) explicit MemoryBlock(size_t length) : _length(length) , _data(new int[length]) { } // 析构器(释放资源) ~MemoryBlock() { if (_data != nullptr) { delete[] _data; } } // 拷贝构造器(实现拷贝语义:拷贝that) MemoryBlock(const MemoryBlock& that) // 拷贝that对象所拥有的资源 : _length(that._length) , _data(new int[that._length]) { std::copy(that._data, that._data + _length, _data); } // 拷贝赋值运算符(实现拷贝语义:释放this + 拷贝that) MemoryBlock& operator=(const MemoryBlock& that) { if (this != &that) { // 释放自身的资源 delete[] _data; // 拷贝that对象所拥有的资源 _length = that._length; _data = new int[_length]; std::copy(that._data, that._data + _length, _data); } return *this; } // 移动构造器(实现移动语义:移动that) MemoryBlock(MemoryBlock&& that) // 将自身的资源指针指向that对象所拥有的资源 : _length(that._length) , _data(that._data) { // 将that对象原本指向该资源的指针设为空值 that._data = nullptr; that._length = 0; } // 移动赋值运算符(实现移动语义:释放this + 移动that) MemoryBlock& operator=(MemoryBlock&& that) { if (this != &that) { // 释放自身的资源 delete[] _data; // 将自身的资源指针指向that对象所拥有的资源 _data = that._data; _length = that._length; // 将that对象原本指向该资源的指针设为空值 that._data = nullptr; that._length = 0; } return *this; }private: size_t _length; // 资源的长度 int* _data; // 指向资源的指针,代表资源本身};MemoryBlock f() { return MemoryBlock(50); }int main(){ MemoryBlock a = f(); // 调用移动构造器,移动语义 MemoryBlock b = a; // 调用拷贝构造器,拷贝语义 MemoryBlock c = std::move(a); // 调用移动构造器,移动语义 a = f(); // 调用移动赋值运算符,移动语义 b = a; // 调用拷贝赋值运算符,拷贝语义 c = std::move(a); // 调用移动赋值运算符,移动语义}
对于转发型引用多说两句:
转发型引用由以下两种语法产生:
auto&&
在模版函数中参数被声明为T&&
这两种都需要推导,都属于转发型引用
好了 再回到我们的移动语义
有一点需要注意的是:移动构造的参数不是const,从上面的例子也可以看到这一点;移动赋值也是一样的。
当=右边是一个右值或是用一个右值来初始化对象时,移动语义最适合了。
当使用std::move之后并不会改变任何事,只是返回一个右值引用(无名)
- 右值引用
- 右值引用
- C++右值引用
- 右值引用
- 右值引用
- C++右值引用
- C++ 右值引用
- C++右值引用
- c++ 右值引用
- C++右值引用
- c++ 右值引用
- c++右值引用
- 右值引用
- 右值引用
- 右值引用
- 右值引用
- C++右值引用
- C++ 右值引用
- 分布式算法3 -- 一致性hash算法
- python自定义库文件路径
- android中xml tools属性部分介绍
- 无法登陆ubuntu问题的一个解决方法
- 关于真机调试的问题
- && 右值引用
- underscore.js
- 第十二条: Comparable和Comparator
- Installing and Configuring a MySQL Database
- C++静态库与动态库
- 从今天起,要养成一下写blog的习惯!
- C语言:error: a label can only be part of a statement and a declaration is not a statement|
- Java面向对象之多态
- xUtils httpUtils 注解