Board logo

标题: C++标准之(ravalue reference) 右值引用介绍 [打印本页]

作者: yuyang911220    时间: 2017-4-21 21:39     标题: C++标准之(ravalue reference) 右值引用介绍

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

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


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

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()函数为:
[url=]复制代码[/url] 代码如下:

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)等技术能避免一些临时对象的产生和拷贝,但某些情况下它们却发挥不了作用,例如:
[url=]复制代码[/url] 代码如下:

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右值。
测试例子如下:
[url=]复制代码[/url] 代码如下:

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;语句符合上述规则三。




欢迎光临 电子技术论坛_中国专业的电子工程师学习交流社区-中电网技术论坛 (http://bbs.eccn.com/) Powered by Discuz! 7.0.0