C++标准之(ravalue reference) 右值引用介绍

1、右值引用引入的背景
临时对象的产生和拷贝所带来的效率折损,一直是C++所为人诟病的问题。但是C++标准允许编译器对于临时对象的产生具有完全的自由度,从而发展出了CopyElision、RVO(包括NRVO)等编译器优化技术,它们可以防止某些情况下临时对象产生和拷贝。下面简单地介绍一下CopyElision、RVO,对此不感兴趣的可以直接跳过:
(1)CopyElision
CopyElision技术是为了防止某些不必要的临时对象产生和拷贝,例如:


代码如下:

structA{
A(int){}
A(constA&){}
};
Aa=42;

理论上讲,上述Aa=42;语句将分三步操作:第一步由42构造一个A类型的临时对象,第二步以临时对象为参数拷贝构造a,第三步析构临时对象。如果A是一个很大的类,那么它的临时对象的构造和析构将造成很大的内存开销。我们只需要一个对象a,为什么不直接以42为参数直接构造a呢?CopyElision技术正是做了这一优化。
【说明】:你可以在A的拷贝构造函数中加一打印语句,看有没有调用,如果没有被调用,那么恭喜你,你的编译器支持CopyElision。但是需要说明的是:A的拷贝构造函数虽然没有被调用,但是它的实现不能没有访问权限,不信你将它放在private权限里试试,编译器肯定会报错。
(2)返回值优化(RVO,ReturnValueOptimization)
返回值优化技术也是为了防止某些不必要的临时对象产生和拷贝,例如:


代码如下:

structA{
A(int){}
A(constA&){}
};
Aget(){returnA(1);}
Aa=get();

理论上讲,上述Aa=get();语句将分别执行:首先get()函数中创建临时对象(假设为tmp1),然后以tmp1为参数拷贝构造返回值(假设为tmp2),最后再以tmp2为参数拷贝构造a,其中还伴随着tmp1和tmp2的析构。如果A是一个很大的类,那么它的临时对象的构造和析构将造成很大的内存开销。返回值优化技术正是用来解决此问题的,它可以避免tmp1和tmp2两个临时对象的产生和拷贝。
【说明】:a)你可以在A的拷贝构造函数中加一打印语句,看有没有调用,如果没有被调用,那么恭喜你,你的编译器支持返回值优化。但是需要说明的是:A的拷贝构造函数虽然没有被调用,但是它的实现不能没有访问权限,不信你将它放在private权限里试试,编译器肯定会报错。
b)除了返回值优化,你可能还听说过一个叫具名返回值优化(NamedReturnValueOptimization,NRVO)的优化技术,从程序员的角度而言,它其实跟RVO同样的逻辑。只是它的临时对象具有变量名标识,例如修改上述get()函数为:


代码如下:

Aget(){
Atmp(1);//#1
//dosomething
returntmp;
}
Aa=get();//#2

想想上述修改后A类型共有几次对象构造?虽然#1处看起来有一次显示地构造,#2处看起来也有一次显示地构造,但如果你的编译器支持NRVO和CopyElision,你会发现整个Aa=get();语句的执行过程,只有一次A对象的构造。如果你在get()函数return语句前打印tmp变量的地址,在Aa=get();语句后打印a的地址,你会发现两者地址相同,这就是应用了NRVO技术的结果。
(3)CopyElision、RVO无法避免的临时对象的产生和拷贝
虽然CopyElision和NVO(包括NRVO)等技术能避免一些临时对象的产生和拷贝,但某些情况下它们却发挥不了作用,例如:


代码如下:

template<typenameT>
voidswap(T&a,T&b){
Ttmp(a);
a=b;
b=tmp;
}

我们只是想交换a和b两个对象所拥有的数据,但却不得不使用一个临时对象tmp备份其中一个对象,如果T类型对象拥有指向(或引用)从堆内存分配的数据,那么深拷贝所带来的内存开销是可以想象的。为此,C++11标准引入了右值引用,使用它可以使临时对象的拷贝具有move语意,从而可以使临时对象的拷贝具有浅拷贝般的效率,这样便可以从一定程度上解决临时对象的深度拷贝所带来的效率折损。

2、C++03标准中的左值与右值
要理解右值引用,首先得区分左值(lvalue)和右值(rvalue)。
C++03标准中将表达式分为左值和右值,并且“非左即右”:
Everyexpressioniseitheranlvalueoranrvalue.
区分一个表达式是左值还是右值,最简便的方法就是看能不能够对它取地址:如果能,就是左值;否则,就是右值。
【说明】:由于右值引用的引入,C++11标准中对表达式的分类不再是“非左即右”那么简单,不过为了简单地理解,我们暂时只需区分左值右值即可,C++11标准中的分类后面会有描述。

3、右值引用的绑定规则
右值引用(rvaluereference,&&)跟传统意义上的引用(reference,&)很相似,为了更好地区分它们俩,传统意义上的引用又被称为左值引用(lvaluereference)。下面简单地总结了左值引用和右值引用的绑定规则(函数类型对象会有所例外):
(1)非const左值引用只能绑定到非const左值;
(2)const左值引用可绑定到const左值、非const左值、const右值、非const右值;
(3)非const右值引用只能绑定到非const右值;
(4)const右值引用可绑定到const右值和非const右值。
测试例子如下:


代码如下:

structA{A(){}};
Alvalue;//非const左值对象
constAconst_lvalue;//const左值对象
Arvalue(){returnA();}//返回一个非const右值对象
constAconst_rvalue(){returnA();}//返回一个const右值对象
//规则一:非const左值引用只能绑定到非const左值
A&lvalue_reference1=lvalue;//ok
A&lvalue_reference2=const_lvalue;//error
A&lvalue_reference3=rvalue();//error
A&lvalue_reference4=const_rvalue();//error
//规则二:const左值引用可绑定到const左值、非const左值、const右值、非const右值
constA&const_lvalue_reference1=lvalue;//ok
constA&const_lvalue_reference2=const_lvalue;//ok
constA&const_lvalue_reference3=rvalue();//ok
constA&const_lvalue_reference4=const_rvalue();//ok
//规则三:非const右值引用只能绑定到非const右值
A&&rvalue_reference1=lvalue;//error
A&&rvalue_reference2=const_lvalue;//error
A&&rvalue_reference3=rvalue();//ok
A&&rvalue_reference4=const_rvalue();//error
//规则四:const右值引用可绑定到const右值和非const右值,不能绑定到左值
constA&&const_rvalue_reference1=lvalue;//error
constA&&const_rvalue_reference2=const_lvalue;//error
constA&&const_rvalue_reference3=rvalue();//ok
constA&&const_rvalue_reference4=const_rvalue();//ok
//规则五:函数类型例外
voidfun(){}
typedefdecltype(fun)FUN;//typedefvoidFUN();
FUN&lvalue_reference_to_fun=fun;//ok
constFUN&const_lvalue_reference_to_fun=fun;//ok
FUN&&rvalue_reference_to_fun=fun;//ok
constFUN&&const_rvalue_reference_to_fun=fun;//ok

【说明】:(1)一些支持右值引用但版本较低的编译器可能会允许右值引用绑定到左值,例如g++4.4.4就允许,但g++4.6.3就不允许了,clang++3.2也不允许,据说VS2010beta版允许,正式版就不允许了,本人无VS2010环境,没测试过。
(2)右值引用绑定到字面值常量同样符合上述规则,例如:int&&rr=123;,这里的字面值123虽然被称为常量,可它的类型为int,而不是constint。对此C++03标准文档4.4.1节及其脚注中有如下说明:
IfTisanon-classtype,thetypeofthervalueisthecv-unqualifiedversionofT.
InC++classrvaluescanhavecv-qualifiedtypes(becausetheyareobjects).ThisdiffersfromISOC,inwhichnon-lvaluesneverhavecv-qualifiedtypes.
因此123是非const右值,int&&rr=123;语句符合上述规则三。

4、C++11标准中的表达式分类
右值引用的引入,使得C++11标准中对表达式的分类不再是非左值即右值那么简单,下图为C++11标准中对表达式的分类:
 
简单解释如下:
(1)lvalue仍然是传统意义上的左值;
(2)xvalue(eXpiringvalue)字面意思可理解为生命周期即将结束的值,它是某些涉及到右值引用的表达式的值(Anxvalueistheresultofcertainkindsofexpressionsinvolvingrvaluereferences),例如:调用一个返回类型为右值引用的函数的返回值就是xvalue。
(3)prvalue(purervalue)字面意思可理解为纯右值,也可认为是传统意义上的右值,例如临时对象和字面值等。
(4)glvalue(generalizedvalue)广义的左值,包括传统的左值和xvalue。
(5)rvalue除了传统意义上的右值,还包括xvalue。
上述lvalue和prvalue分别跟传统意义上的左值和右值概念一致,比较明确,而将xvalue描述为『某些涉及到右值引用的表达式的值』,某些是哪些呢?C++11标准给出了四种明确为xvalue的情况:


代码如下:

[Note:Anexpressionisanxvalueifitis:
--theresultofcallingafunction,whetherimplicitlyorexplicitly,whosereturntypeisanrvaluereferencetoobjecttype,
--acasttoanrvaluereferencetoobjecttype,
--aclassmemberaccessexpressiondesignatinganon-staticdatamemberofnon-referencetypeinwhichtheobjectexpressionisanxvalue,or
--a.*pointer-to-memberexpressioninwhichthefirstoperandisanxvalueandthesecondoperandisapointertodatamember.
Ingeneral,theeffectofthisruleisthatnamedrvaluereferencesaretreatedaslvaluesandunnamedrvaluereferencestoobjectsaretreatedasxvalues;rvaluereferencestofunctionsaretreatedaslvalueswhethernamedornot.--endnote]
[Example:
structA{
intm;
};
A&&operator+(A,A);
A&&f();
Aa;
A&&ar=static_cast<A&&>(a);
Theexpressionsf(),f().m,static_cast<A&&>(a),anda+aarexvalues.Theexpressionarisanlvalue.
--endexample]

简单地理解就是:具名的右值引用(namedrvaluereference)属于左值,不具名的右值引用(unamedrvaluereference)就属于xvalue,而引用函数类型的右值引用不论是否具名都当做左值处理。看个例子更容易理解:
[/code]
Arvalue(){returnA();}
A&&rvalue_reference(){returnA();}
fun();//返回的是不具名的右值引用,属于xvalue
A&&ra1=rvalue();//ra1是具名右值应用,属于左值
A&&ra2=ra1;//error,ra1被当做左值对待,因此ra2不能绑定到ra1(不符合规则三)
A&la=ra1;//ok,非const左值引用可绑定到非const左值(符合规则一)


代码如下:

5、move语意
现在,我们重新顾到1-(3),其中提到move语意,那么怎样才能使临时对象的拷贝具有move语意呢?下面我们以一个类的实现为例:
[code]
classA{
public:
A(constchar*pstr=0){m_data=(pstr!=0?strcpy(newchar[strlen(pstr)+1],pstr):0);}
//copyconstructor
A(constA&a){m_data=(a.m_data!=0?strcpy(newchar[strlen(a.m_data)+1],a.m_data):0);}
//copyassigment
A&operator=(constA&a){
if(this!=&a){
delete[]m_data;
m_data=(a.m_data!=0?strcpy(newchar[strlen(a.m_data)+1],a.m_data):0);
}
return*this;
}
//moveconstructor
A(A&&a):m_data(a.m_data){a.m_data=0;}
//moveassigment
A&operator=(A&&a){
if(this!=&a){
m_data=a.m_data;
a.m_data=0;
}
return*this;
}
~A(){delete[]m_data;}
private:
char*m_data;
};

从上例可以看到,除了传统的拷贝构造(copyconstructor)和拷贝赋值(copyassigment),我们还为A类的实现添加了移动拷贝构造(moveconstructor)和移动赋值(moveassigment)。这样,当我们拷贝一个A类的(右值)临时对象时,就会使用具有move语意的移动拷贝构造函数,从而避免深拷贝中strcpy()函数的调用;当我们将一个A类的(右值)临时对象赋值给另一个对象时,就会使用具有move语意的移动赋值,从而避免拷贝赋值中strcpy()函数的调用。这就是所谓的move语意。

6、std::move()函数的实现
了解了move语意,那么再来看1-(3)中的效率问题:


代码如下:

template<typenameT>//如果T是classA
voidswap(T&a,T&b){
Ttmp(a);//根据右值引用的绑定规则三可知,这里不会调用moveconstructor,而会调用copyconstructor
a=b;//根据右值引用的绑定规则三可知,这里不会调用moveassigment,而会调用copyassigment
b=tmp;//根据右值引用的绑定规则三可知,这里不会调用moveassigment,而会调用copyassigment
}

从上例可以看到,虽然我们实现了moveconstructor和moveassigment,但是swap()函数的例子中仍然使用的是传统的copyconstructor和copyassigment。要让它们真正地使用move语意的拷贝和复制,就该std::move()函数登场了,看下面的例子:


代码如下:

voidswap(A&a,A&b){
Atmp(std::move(a));//std::move(a)为右值,这里会调用moveconstructor
a=std::move(b);//std::move(b)为右值,这里会调用moveassigment
b=std::move(tmp);//std::move(tmp)为右值,这里会调用moveassigment
}

我们不禁要问:我们通过右值应用的绑定规则三和规则四,知道右值引用不能绑定到左值,可是std::move()函数是如何把上述的左值a、b和tmp变成右值的呢?这就要从std::move()函数的实现说起,其实std::move()函数的实现非常地简单,下面以libcxx库中的实现(在<type_trait>头文件中)为例:


代码如下:

template<class_Tp>
inlinetypenameremove_reference<_Tp>::type&&move(_Tp&&__t){
typedeftypenameremove_reference<_Tp>::type_Up;
returnstatic_cast<_Up&&>(__t);
}

其中remove_reference的实现如下:


代码如下:

template<class_Tp>structremove_reference{typedef_Tptype;};
template<class_Tp>structremove_reference<_Tp&>{typedef_Tptype;};
template<class_Tp>structremove_reference<_Tp&&>{typedef_Tptype;};

从move()函数的实现可以看到,move()函数的形参(Parameter)类型为右值引用,它怎么能绑定到作为实参(Argument)的左值a、b和tmp呢?这不是仍然不符合右值应用的绑定规则三嘛!简单地说,如果move只是个普通的函数(而不是模板函数),那么根据右值应用的绑定规则三和规则四可知,它的确不能使用左值作为其实参。但它是个模板函数,牵涉到模板参数推导,就有所不同了。C++11标准文档14.8.2.1节中,关于模板函数参数的推导描述如下:
Templateargumentdeductionisdonebycomparingeachfunctiontemplateparametertype(callitP)withthetypeofthecorrespondingargumentofthecall(callitA)asdescribedbelow.(14.8.2.1.1)
IfPisareferencetype,thetypereferredtobyPisusedfortypededuction.IfPisanrvaluereferencetoacvunqualifiedtemplateparameterandtheargumentisanlvalue,thetype"lvaluereferencetoA"isusedinplaceofAfortypededuction.(14.8.2.1.3)
大致意思是:模板参数的推导其实就是形参和实参的比较和匹配,如果形参是一个引用类型(如P&),那么就使用P来做类型推导;如果形参是一个cv-unqualified(没有const和volatile修饰的)右值引用类型(如P&&),并且实参是一个左值(如类型A的对象),就是用A&来做类型推导(使用A&代替A)。


代码如下:

template<class_Tp>voidf(_Tp&&){/*dosomething*/}
template<class_Tp>voidg(const_Tp&&){/*dosomething*/}
intx=123;
f(x);//ok,f()模板函数形参为非const非volatile右值引用类型,实参x为int类型左值,使用int&来做参数推导,因此调用f<int&>(int&)
f(456);//ok,实参为右值,调用f<int>(int&&)
g(x);//error,g()函数模板参数为const右值引用类型,会调用g<int>(constint&&),通过右值引用规则四可知道,const右值引用不能绑定到左值,因此会导致编译错误

了解了模板函数参数的推导过程,已经不难理解std::move()函数的实现了,当使用左值(假设其类型为T)作为参数调用std::move()函数时,实际实例化并调用的是std::move<T&>(T&),而其返回类型T&&,这就是move()函数左值变右值的过程(其实左值本身仍是左值,只是被当做右值对待而已,被人“抄了家”,变得一无所有)。
【说明】:C++的始祖BjarneStroustrup说:如果move()函数改名为rval()可能会更好些,但是move()这个名字已经被使用了好些年了(Maybeitwouldhavebeenbetterifmove()hadbeencalledrval(),butbynowmove()hasbeenusedforyears.)。

7、完整的示例
至此,我们已经了解了不少右值引用的知识点了,下面给出了一个完整地利用右值引用实现move语意的例子:


代码如下:

#include<iostream>
#include<cstring>
#definePRINT(msg)do{std::cout<<msg<<std::endl;}while(0)
template<class_Tp>structremove_reference{typedef_Tptype;};
template<class_Tp>structremove_reference<_Tp&>{typedef_Tptype;};
template<class_Tp>structremove_reference<_Tp&&>{typedef_Tptype;};
template<class_Tp>
inlinetypenameremove_reference<_Tp>::type&&move(_Tp&&__t){
typedeftypenameremove_reference<_Tp>::type_Up;
returnstatic_cast<_Up&&>(__t);
}
classA{
public:
A(constchar*pstr){
PRINT("constructor");
m_data=(pstr!=0?strcpy(newchar[strlen(pstr)+1],pstr):0);
}
A(constA&a){
PRINT("copyconstructor");
m_data=(a.m_data!=0?strcpy(newchar[strlen(a.m_data)+1],a.m_data):0);
}
A&operator=(constA&a){
PRINT("copyassigment");
if(this!=&a){
delete[]m_data;
m_data=(a.m_data!=0?strcpy(newchar[strlen(a.m_data)+1],a.m_data):0);
}
return*this;
}
A(A&&a):m_data(a.m_data){
PRINT("moveconstructor");
a.m_data=0;
}
A&operator=(A&&a){
PRINT("moveassigment");
if(this!=&a){
m_data=a.m_data;
a.m_data=0;
}
return*this;
}
~A(){PRINT("destructor");delete[]m_data;}
private:
char*m_data;
};
voidswap(A&a,A&b){
Atmp(move(a));
a=move(b);
b=move(tmp);
}
intmain(intargc,char**argv,char**env){
Aa("123"),b("456");
swap(a,b);
return0;
}

输出结果为:


代码如下:

constructor
constructor
moveconstructor
moveassigment
moveassigment
destructor
destructor
destructor

8、幕后花絮
C++11标准引入右值引用的提案是由HowardHinnant提出的,它的最初提案N1377在02年就提出来了,中间经历了多次修改N1385、N1690、N1770、N1855、N1952、N2118。包括它的最终版本N2118在内,HowardHinnant的提案中都使用了右值引用直接绑定到左值的例子,并且由HowardHinnant、BjarneStroustrup和BronekKozicki三人08年10月共同署名的《ABriefIntroductiontoRvalueReferences》文章中也有右值引用直接绑定到左值的例子,但奇怪的是11年公布的最新的C++11标准文档中却不允许右值引用直接绑定到左值,其中的原因不得而知,但从中不难理解为什么早些编译器版本(如g++4.4.4)对右值引用绑定到左值,不会报出编译错误,而最新的编译器却会报错。
另外,HowardHinnant是C++标准委员会LibraryWorkingGroup老大(chairman),libcxx和libcxxabi的维护者,苹果公司的高级软件工程师。

(0)

相关推荐

  • 浅析C++中结构体的定义、初始化和引用

    定义:结构体(struct)是由一系列具有相同类型或不同类型的数据构成的数据集合,也叫结构. 声明一个结构体类型的形式是: 复制代码 代码如下: struct Student{      //声明一个结构体类型Student  int num;         //声明一个整形变量num  char name[20];   //声明一个字符型数组name  char sex;        //声明一个字符型变量sex  int age;         //声明一个整形变量age  float

  • 从C语言过渡到C++之引用(别名)

    今天要讲的是C++中我最喜欢的一个用法--引用,也叫别名. 引用就是给一个变量领取一个变量名,方便我们间接地使用这个变量.我们可以给一个变量创建N个引用,这N + 1个变量共享了同一块内存区域. 1. 声明引用 创建引用的格式如下: 数据类型 引用名 = 原变量 比如: int a = 1; int& b = a; 在这段代码中,我们给变量a创建了一个别名b.它们公用同一块内存区域,就是创建变量a时申请的区域. 注意:由于引用并不需要申请一块新的内存空间,因此在建立引用时只能声明,不能定义. 面

  • 深入解析C++中的引用类型

    c++比起c来除了多了类类型外还多出一种类型:引用.这个东西变量不象变量,指针不象指针,我以前对它不太懂,看程序时碰到引用都稀里糊涂蒙过去.最近把引用好好地揣摩了一番,小有收获,特公之于社区,让初学者们共享. 引用指的是对一个对象的引用.那么什么是对象?在c++中狭义的对象指的是用类,结构,联合等复杂数据类型来声明的变量,如 MyClass myclass,CDialog  mydlg,等等.广义的对象还包括用int,char,float等简单类型声明的变量,如int a,char b等等.我在

  • 探讨:C++中函数返回引用的注意事项

    函数 返回值 和 返回引用 是不同的函数返回值时会产生一个临时变量作为函数返回值的副本,而返回引用时不会产生值的副本,既然是引用,那引用谁呢?这个问题必须清楚,否则将无法理解返回引用到底是个什么概念.以下是几种引用情况:1,引用函数的参数,当然该参数也是一个引用 复制代码 代码如下: const string &shorterString(const string &s1,const string &s2)      {             return s1.size()&l

  • C++中引用&与取地址&的区别分析

    C++中的引用&与取址&是很多初学者经常容易出错的地方,今天本文就对此加以分析总结,供大家参考之用. 具体而言,一个是用来传值的 一个是用来获取首地址的 &(引用)==>出现在变量声明语句中位于变量左边时,表示声明的是引用.      例如: int &rf; // 声明一个int型的引用rf &(取地址运算符)==>在给变量赋初值时出现在等号右边或在执行语句中作为一元运算符出现时表示取对象的地址. 在C++中,既有引用又有取地址,好多人对引用和取地址不

  • C++中引用的使用总结

    1引用的定义 引用时C++对C的一个重要的扩充,引用的作用是给变量起一个别名. 例如: int a; int &b=a;//声明b是a的引用 经过以上的声明,b就成为了a的别名,a和b的地位以及作用都是一样的. 将b声明为a的引用,不需要再为b开辟新的单元,b和a在内存中占同一存储单元,它们具有相同的地址. 复制代码 代码如下: #include<iostream>using namespace std;int main(){         int a=10;         int

  • C++中对象的常引用总结

    直接传递对象名 用对象名做函数参数时,在函数调用时将建立一个新的对象,它是形参对象的拷贝. ================下面给出一个直接传递对象名的例子程序1.1================== 复制代码 代码如下: #include<iostream>using namespace std;class Time { public:  Time(int,int,int);//构造函数   void Print();//输出信息函数   void reset(Time t);//重置函数

  • C++标准之(ravalue reference) 右值引用介绍

    1.右值引用引入的背景 临时对象的产生和拷贝所带来的效率折损,一直是C++所为人诟病的问题.但是C++标准允许编译器对于临时对象的产生具有完全的自由度,从而发展出了CopyElision.RVO(包括NRVO)等编译器优化技术,它们可以防止某些情况下临时对象产生和拷贝.下面简单地介绍一下CopyElision.RVO,对此不感兴趣的可以直接跳过: (1)CopyElision CopyElision技术是为了防止某些不必要的临时对象产生和拷贝,例如: 复制代码 代码如下: structA{ A(

  • C++左值与右值,右值引用,移动语义与完美转发详解

    目录 C++——左值与右值.右值引用.移动语义与完美转发 一.左值和右值的定义 二.如何判断一个表达式是左值还是右值(大多数场景) 三.C++右值引用 四.std::move()与移动语义 五. 完美转发 总结 C++——左值与右值.右值引用.移动语义与完美转发 在C++或者C语言中,一个表达式(可以是字面量.变量.对象.函数的返回值等)根据其使用场景不同,分为左值表达式和右值表达式. 一.左值和右值的定义 1.左值的英文为locator value,简写为lvalue,可意为存储在内存中.有明

  • 深入解读C++中的右值引用

    右值引用(及其支持的Move语意和完美转发)是C++0x将要加入的最重大语言特性之一,这点从该特性的提案在C++ - State of the Evolution列表上高居榜首也可以看得出来. 从实践角度讲,它能够完美解决C++中长久以来为人所诟病的临时对象效率问题.从语言本身讲,它健全了C++中的引用类型在左值右值方面的缺陷.从库设计者的角度讲,它给库设计者又带来了一把利器.从库使用者的角度讲,不动一兵一卒便可以获得"免费的"效率提升- 在标准C++语言中,临时量(术语为右值,因其出

  • C++11右值引用和转发型引用教程详解

    右值引用 为了解决移动语义及完美转发问题,C++11标准引入了右值引用(rvalue reference)这一重要的新概念.右值引用采用T&&这一语法形式,比传统的引用T&(如今被称作左值引用 lvalue reference)多一个&. 如果把经由T&&这一语法形式所产生的引用类型都叫做右值引用,那么这种广义的右值引用又可分为以下三种类型: 无名右值引用 具名右值引用 转发型引用 无名右值引用和具名右值引用的引入主要是为了解决移动语义问题. 转发型引用的引

  • 深入了解c++11 移动语义与右值引用

    1.移动语义 C++11新标准中一个最主要的特性就是提供了移动而非拷贝对象的能力.如此做的好处就是,在某些情况下,对象拷贝后就立即被销毁了,此时如果移动而非拷贝对象会大幅提升性能.参考如下程序: //moveobj.cpp #include <iostream> #include <vector> using namespace std; class Obj { public: Obj(){cout <<"create obj" << e

  • 一篇文章弄懂C++左值引用和右值引用

    目录 1. 左值和右值 2. 左值引用 3. 右值引用 3.1 出现 3.2 概念 3.3 应用 3.3.1 右值引用绑定到左值上 3.3.2 std::move()本质 3.3.3 移动构造函数和移动赋值运算符 3.3.4 std::move()的一个例子 4. 补充-协助完成返回值优化(RVO) 5. 总结 篇幅较长,算是从0开始介绍的,请耐心看~ 该篇介绍了左值和右值的区别.左值引用的概念.右值引用的概念.std::move()的本质.移动构造函数.移动复制运算符和RVO. 1. 左值和右

  • C++右值引用与移动构造函数基础与应用详解

    目录 1.右值引用 1.1左值右值的纯右值将亡值右值 1.2右值引用和左值引用 2.移动构造函数 2.1完美的移动转发 1.右值引用 右值引用是 C++11 引入的与 Lambda 表达式齐名的重要特性之一.它的引入解决了 C++ 中大量的历史遗留问题, 消除了诸如 std::vector.std::string 之类的额外开销, 也才使得函数对象容器 std::function 成为了可能. 1.1左值右值的纯右值将亡值右值 要弄明白右值引用到底是怎么一回事,必须要对左值和右值做一个明确的理解

  • 详解C++11中的右值引用与移动语义

    C++11的一个最主要的特性就是可以移动而非拷贝对象的能力.很多情况都会发生对象的拷贝,有时对象拷贝后就立即销毁,在这些情况下,移动而非拷贝对象会大幅度提升性能. 右值与右值引用 为了支持移动操作,新标准引入了一种新的引用类型--右值引用,就是必须绑定到右值的引用.我们通过&&而不是&来获得右值引用.右值引用一个重要的特性就是只能绑定到将要销毁的对象. 左值和右值是表达式的属性,一些表达式生成或要求左值,而另一些则生成或要求右值.一般而言,一个左值表达式表示的是一个对象的身份,而右

  • 浅析C++11中的右值引用、转移语义和完美转发

    1. 左值与右值: C++对于左值和右值没有标准定义,但是有一个被广泛认同的说法:可以取地址的,有名字的,非临时的就是左值;不能取地址的,没有名字的,临时的就是右值. 可见立即数,函数返回的值等都是右值;而非匿名对象(包括变量),函数返回的引用,const对象等都是左值. 从本质上理解,创建和销毁由编译器幕后控制的,程序员只能确保在本行代码有效的,就是右值(包括立即数);而用户创建的,通过作用域规则可知其生存期的,就是左值(包括函数返回的局部变量的引用以及const对象),例如: int& fo

  • C++ lambda 捕获模式与右值引用的使用

    lambda 表达式和右值引用是 C++11 的两个非常有用的特性. lambda 表达式实际上会由编译器创建一个 std::function 对象,以值的方式捕获的变量则会由编译器复制一份,在 std::function 对象中创建一个对应的类型相同的 const 成员变量,如下面的这段代码: int main(){ std::string str = "test"; printf("String address %p in main, str %s\n", &a

随机推荐