c++11右值引用
来源:互联网 发布:python怎么输入 编辑:程序博客网 时间:2024/06/06 01:49
c++11中引入了右值引用和移动语义,可以避免无谓的复制,提高了程序的性能。
我们平常所说的引用通常是指左值引用,用&表示。而右值引用,用&&表示。
要介绍右值引用的作用以及如何使用之前,我们必须要搞明白什么是左值,什么是右值。
左值与右值
左值:指表达式结束后依然存在的持久对象。
右值:指表达式结束时就不再存在的临时对象。
一个区分左值与右值的便捷方法:
看看能不能对表达式取地址,如果能,则为左值,否则为右值。
所有的具名变量或对象都是左值,而右值不具名。
举例:
int a = 10; int b = 20; int *pFlag = &a; vector<int> vctTemp; vctTemp.push_back(100); string str1 = string("hello"); string str2 = "world"; const int &m = 1;
请问,a,b, a+b, a++, ++a, pFlag, *pFlag, vctTemp[0], 100, string(“hello”), str1, str1+str2, m分别是左值还是右值?
a和b都是持久对象(可以对其取地址),是左值;
a+b是临时对象(不可以对其取地址),是右值;
a++是先取出持久对象a的一份拷贝,再使持久对象a的值加1,最后返回那份拷贝,而那份拷贝是临时对象(不可以对其取地址),故其是右值;
++a则是使持久对象a的值加1,并返回那个持久对象a本身(可以对其取地址),故其是左值;
pFlag和*pFlag都是持久对象(可以对其取地址),是左值;
vctTemp[0]调用了重载的[]操作符,而[]操作符返回的是一个int &,为持久对象(可以对其取地址),是左值;
100和string(“hello”)是临时对象(不可以对其取地址),是右值;
str1是持久对象(可以对其取地址),是左值;
str1+str2是调用了+操作符,而+操作符返回的是一个string(不可以对其取地址),故其为右值;
m是一个常量引用,引用到一个右值,但引用本身是一个持久对象(可以对其取地址),为左值
右值引用
右值引用就是一个对右值进行引用的类型。
无论声明左值引用还是右值引用都必须立即进行初始化,因为引用类型本身并不拥有所绑定对象的内存,只是该对象的一个别名。
举例:
int i=42;int &r=i; //正确:r引用iint &&rr=i; //错误:不能将一个右值引用绑定到一个左值上int &r2=i*42; //错误:i*42是一个右值const int &r3=i*42; //正确:我们可以将一个const的左值引用绑定到一个右值上int &&rr2=i*42; //正确:将rr2绑定到乘法结果上
说明:
- 返回左值引用的函数,连同赋值,下标,解引用和前置递增/递减运算符,都是返回左值的表达式的例子。我们可以将一个左值引用绑定到这类表达式的结果上。
- 返回非引用类型的函数,连同算数,关系,位以及后置递增/递减运算符,都生成右值。我们不能将一个左值引用绑定到这类表达式上,但我们可以将一个const的左值引用或者一个右值引用绑定到这类表达式上。
右值引用的作用
通过右值引用的定义,我们可以知道,右值引用只能绑定到一个将要销毁的对象。我们可以通过右值引用的声明,使该右值又“重获新生”,其生命周期与右值引用类型变量的生命周期一样,只要该变量还活着,该右值临时量将会一直存活下去。这样,我们可以通过该右值引用继续使用该右值的数据。
看一下下面的代码:
#include<iostream>using namespace std;int g_constructCount=0;int g_copyConstructCount=0;int g_destructCount=0;struct A{ A(){ cout<<"construct: "<<++g_constructCount<<endl; } A(const A& a) { cout<<"copy construct: "<<++g_copyConstructCount<<endl; } ~A() { cout<<"destruct: "<<++g_destructCount<<endl; }};A GetA(){ return A();}int main(){ A a=GetA(); return 0;}
为了清楚地观察临界值,在GCC下编译时设置编译选项-fno-elide-constructors来关闭返回值优化结果(返回值优化将会把临时对象优化掉)。
运行结果:
construct: 1
copy construct: 1
destruct: 1
copy construct: 2
destruct: 2
destruct: 3
从上面例子中可以看到,在没有返回值优化的情况下,拷贝构造函数调用了两次,一次是GeTA()函数内部创建的对象返回后构造一个临时对象产生的,另一次是在main函数中构造a对象产生的。第二次的destruct是因为临时对象在构造a对象之后就摧毁了。
上述例子中,在main函数中创建了一个类A的对象a,并将GetA()返回的临时对象拷贝给a。如果在上面的代码中我们通过右值引用来绑定函数的返回值,结果又会怎么样呢?
#include<iostream>using namespace std;int g_constructCount=0;int g_copyConstructCount=0;int g_destructCount=0;struct A{ A(){ cout<<"construct: "<<++g_constructCount<<endl; } A(const A& a) { cout<<"copy construct: "<<++g_copyConstructCount<<endl; } ~A() { cout<<"destruct: "<<++g_destructCount<<endl; }};A GetA(){ return A();}int main(){ const A &&a=GetA(); return 0;}
gcc file.c -fno-elide-constructors 运行结果如下:
construct: 1
copy construct: 1
destruct: 1
destruct: 2
我们会发现,通过右值引用,比之前少了一次拷贝构造函数和一次析构函数,原因在于右值引用绑定了右值,让临时右值的生命周期延长了。我们可以利用这个特点做一些性能优化,即避免临时对象的拷贝构造和析构。事实上,通过常量左值引用也经常用来做性能优化。将上面的代码改成:const A&a=GetA();
输出结果与右值引用一样,因为常量左值引用是一个“万能”的引用类型,既可以接受非常量左值,常量左值,又可以接受非常量右值,常量右值。
特例:自动类型推断时的引用折叠
当在发生自动类型推断时(如函数模板的类型自动推导,或auto关键字),T&&并不一定表示右值,它绑定的类型是未定的,既可能是左值又可能是右值。
看看下面的例子:
template<class T>void f(T&& param);f(10); //10是右值int x=10;f(x); //x虽然是左值,但是正确
从上面的例子可以看出,param有时是左值,有时是右值。这表示param实际上是一个未定的引用类型,它是左值还是右值引用取决于它的初始化,如果T&&被一个左值初始化,它就是一个左值;如果它被一个右值初始化,它就是一个右值。
当我们将10传入f函数时,因为10是int类型右值,所以T会被推导为int。
而当我们将左值x对象传入f函数时,T会被推导为int&类型,此时函数参数为int & &¶m,此时就涉及到引用折叠,int& &&等价于int&类型。
引用折叠:
- 所有的右值引用叠加到右值引用上仍然还是一个右值引用。(例:T&& &&折叠成T&&)
- 所有的其他引用类型之间的折叠都将变成左值引用。 (例:T& &&折叠成T&)
需要我们注意的是,未定的引用类型只在T&&下发生,任何一点附加条件都会使之失效,而变成一个普通的右值引用。
比如:
template<class T>void f(const T&¶m);int x=10;f(x); //错误,此时param是一个右值引用,x为左值,无法传给param
std::move
通过上面的介绍,我们先看一下下面的代码:
int w1,w2;auto&& v1=w1; //v1被推导为int&类型decltype(w1)&& v2=w2; //错误
v1是一个未定的引用类型,它被一个左值初始化,所以它最终是一个左值;v2是一个右值引用类型,但它被一个左值初始化,一个左值初始化一个右值引用类型是不合法的,所以编译器会报错。
但是,如果我们希望把一个左值赋给一个右值引用类型该怎么做呢?
此时就用到了标准库move函数。
decltype(w1)&& v2=sstd::move(w2); //正确
虽然我们不能将一个右值引用直接绑定到一个左值上,但我们可以显示地将一个左值转换为对应的右值引用类型。我们可以通过调用一个名为move的新标准库函数来获得绑定到左值上的右值引用。
我们知道,右值表示的是一个临时对象或即将被摧毁的对象,当我们调用move函数返回一个左值对象的右值引用类型时,就意味着承诺:除了对该对象赋值或摧毁它外,我们将不再使用它。我们不能对调用move后的对象的值做任何假设。
- [C++]C++11右值引用
- C++/C++11中左值、左值引用、右值、右值引用的使用
- 【C++】C++11特性:右值引用与转发
- c++move语义与右值引用
- C++:右值引用与移动构造
- [C++]右值引用和转移语义
- C++primer学习笔记--右值引用
- 【C++】右值引用与模板
- C++11右值引用
- C++ 11右值引用
- c++11 右值引用
- C++ 11右值引用
- C++11 右值引用
- C++ 11右值引用
- C++ 11右值引用
- C++ 11右值引用
- C++11右值引用
- C++ 11右值引用
- 51nod 1222 最小公倍数计数
- 用java操作cvs,实现java对文件的读写
- 数组初始化
- TypeScript生成随机数
- html加载背景图片
- c++11右值引用
- BlueROV-15: Modify the Drive Modes in Mavlink
- 第一课:Python读取.csv文件
- list\set等容器(集合)那里重写equals为什么还要重写hashCode方法
- 23种设计模式4--行为型模式(策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式)
- java eclipse 环境配置
- Java中synchronized的实现原理
- 符号扩展和无符号扩展
- 邻接矩阵的创建