C++标准之(ravalue reference) 右值引用介绍(3)
- UID
- 1029342
- 性别
- 男
|
C++标准之(ravalue reference) 右值引用介绍(3)
其中remove_reference的实现如下:
[url=]复制代码[/url] 代码如下:
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)。
[url=]复制代码[/url] 代码如下:
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语意的例子:
[url=]复制代码[/url] 代码如下:
#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;
}
输出结果为:
[url=]复制代码[/url] 代码如下:
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的维护者,苹果公司的高级软件工程师。 |
|
|
|
|
|