首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

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

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

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的情况:
[url=]复制代码[/url] 代码如下:

[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左值(符合规则一)
[url=]复制代码[/url] 代码如下:

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)中的效率问题:
[url=]复制代码[/url] 代码如下:

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()函数登场了,看下面的例子:
[url=]复制代码[/url] 代码如下:

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>头文件中)为例:
[url=]复制代码[/url] 代码如下:

template<class_Tp>
inlinetypenameremove_reference<_Tp>::type&&move(_Tp&&__t){
typedeftypenameremove_reference<_Tp>::type_Up;
returnstatic_cast<_Up&&>(__t);
}
继承事业,薪火相传
返回列表