轉(zhuǎn)發(fā)的問題
????在模板編程中忱嘹,常有一種場景是把模板參數(shù)轉(zhuǎn)發(fā)給另一個函數(shù)調(diào)用疏之,這時候如果只提供值傳遞版本會顯得效率太低州弟∪月桑看一下代碼
template<class TYPE, class ARG>
TYPE* get_instance(ARG arg)
{
TYPE* ret;
ret = new TYPE(arg);
return ret;
}
????代碼很簡單嘿悬,就是用ARG參數(shù)去初始化一個TYPE類型的對象,然后返回該對象指針水泉∩普牵考慮一下如果ARG類型是一個自定義類型,那么這樣的值傳遞會是比較大的性能開銷草则。有沒有辦法改進一下钢拧?看一下代碼
template<class TYPE, class ARG>
TYPE* get_instance(const ARG& arg)
{
TYPE* ret;
ret = new TYPE(arg);
return ret;
}
????這段代碼將傳入改為了萬能的常量左值引用,可以接受任何類型炕横,可以解決性能開銷的問題源内,但是不夠靈活,如果我們想TYPE接受一個右值去初始化呢看锉?那么有沒有可以把參數(shù)連同類型一起轉(zhuǎn)發(fā)的方案呢姿锭?C++11提供了這樣能力既完美轉(zhuǎn)發(fā)。代碼如下
template<class TYPE, class ARG>
TYPE* get_instance(ARG&& arg)
{
TYPE* ret;
ret = new TYPE(std::forward<ARG>(arg));
return ret;
}
形參改為了右值引用么伯铣?
兩個模板函數(shù)推導(dǎo)原則
????解釋一下前面留下的問題呻此。在模板函數(shù)中(注意是模板函數(shù)不是模板類,也不是普通函數(shù)腔寡,只是模板函數(shù))焚鲜,形如上例中的形參即T&&,如果T是一個推導(dǎo)類型(即模板參數(shù))它的意思并不是右值引用,它有一個專用的名字forwarding reference忿磅,它遵循以下的特殊推導(dǎo)規(guī)則來推導(dǎo)T的類型:
1糯彬、引用折疊原則(reference collapsing rule),下面以int為類型舉例葱她,以區(qū)別于模板參數(shù)T
a)int& & (引用的引用) 被轉(zhuǎn)化成 int&
b)int&& & (rvalue的引用)被傳化成 int&
c)int& && (引用作rvalue) 被轉(zhuǎn)化成 int&
d)int&& && 被轉(zhuǎn)化成 int&&
2撩扒、推導(dǎo)原則
以上面模板函數(shù)為例,三個特殊之處(左值引用吨些,常量左值引用如預(yù)期被推導(dǎo)出來)
a)如果傳入get_instance的參數(shù)是int類型(即左值)搓谆,那么ARG會被推導(dǎo)為int&,即ARG=int&
b)如果傳入get_instance的參數(shù)是右值int類型(即右值)豪墅,那么ARG被推導(dǎo)為int泉手,即ARG=int
c)如果傳入get_instance的參數(shù)是const int類型(即常量左值),那么ARG被推導(dǎo)為const int&偶器,即ARG=const int&
注意:為什么這里沒有參數(shù)類型是右值引用類型的參數(shù)斩萌??
其實右值引用是一個左值屏轰,即你傳入函數(shù)的實參類型有可能是一個左值(左值引用颊郎,左值,常量左值引用霎苗,右值引用)袭艺,也有可能是一個右值。注意到細微的區(qū)別了么叨粘?這里本章最后會給出一些代碼示例。
以上三個特殊推導(dǎo)之處
結(jié)合std::forward代碼來看一下
template <class _Tp>
inline
_Tp&&
forward(typename remove_reference<_Tp>::type& __t)
{
return static_cast<_Tp&&>(__t);
}
template<class TYPE, class ARG>
TYPE* get_instance(ARG&& arg)
{
TYPE* ret;
ret = new TYPE(std::forward<ARG>(arg));
return ret;
}
現(xiàn)在結(jié)合上面兩個原則來推導(dǎo)一下:
a)調(diào)用get_instance傳入int類型瘤睹,ARG推導(dǎo)為int&升敲,傳遞給std::forward的模板參數(shù)就是int&類型,std::forward中的typename remove_reference<_Tp>::type為int類型轰传,static_cast中_Tp&&類型根據(jù)引用折疊驴党,推導(dǎo)為int& &&即int&,最后forward轉(zhuǎn)發(fā)為int&類型获茬,完美轉(zhuǎn)發(fā)港庄。
b)調(diào)用get_instance傳入int右值類型,ARG推導(dǎo)為int恕曲,傳遞給std::forward的模板參數(shù)就是int類型鹏氧,std::forward中的typename remove_reference<_Tp>::type為int類型,static_cast中_Tp&&類型根據(jù)引用折疊佩谣,推導(dǎo)為int&&即int&&把还,最后forward轉(zhuǎn)發(fā)為int&&類型,完美轉(zhuǎn)發(fā)。
c)調(diào)用get_instance傳入const int類型吊履,ARG推導(dǎo)為const int&安皱,傳遞給std::forward的模板參數(shù)就是const int&類型,std::forward中的typename remove_reference<_Tp>::type為const int類型艇炎,static_cast中_Tp&&類型根據(jù)引用折疊酌伊,推導(dǎo)為const int& &&即const int&,最后forward轉(zhuǎn)發(fā)為const int&類型缀踪,完美轉(zhuǎn)發(fā)居砖。
d)調(diào)用get_instance傳入int&類型,ARG推導(dǎo)為int&辜贵,傳遞給std::forward的模板參數(shù)就是int&類型悯蝉,std::forward中的typename remove_reference<_Tp>::type為int類型,static_cast中_Tp&&類型根據(jù)引用折疊托慨,推導(dǎo)為int& &&即int&鼻由,最后forward轉(zhuǎn)發(fā)為int&類型,完美轉(zhuǎn)發(fā)厚棵。
e)調(diào)用get_instance傳入const int&類型蕉世,ARG推導(dǎo)為const int&,傳遞給std::forward的模板參數(shù)就是const int&類型婆硬,std::forward中的typename remove_reference<_Tp>::type為int類型狠轻,static_cast中_Tp&&類型根據(jù)引用折疊,推導(dǎo)為const int& &&即const int&彬犯,最后forward轉(zhuǎn)發(fā)為const int&類型向楼,完美轉(zhuǎn)發(fā)。
完美轉(zhuǎn)發(fā)
????上面分析的情況即為完美轉(zhuǎn)發(fā)谐区,總結(jié)一下是模板函數(shù)形參形如T&&湖蜕,結(jié)合庫函數(shù)std::forward來將參數(shù)實現(xiàn)類型的完美轉(zhuǎn)發(fā)。
代碼示例
????用代碼來補充說明一下上面留下的問題宋列。
template <typename T>
void
wrapper(T&& value)
{
std::cout<<"T is a ref type(lvalue or rvalue ref)?:"<<std::is_reference<T>::value<<std::endl;
std::cout<<"T is right value ref?:"<<std::is_rvalue_reference<T>::value<<std::endl; //none T is deduce to rvalue ref
}
static void execute()
{
int left = 1;
int &&right = 2;
wrapper(right); //input is a lvalue
wrapper(3); //intput is a rvalue
wrapper(std::move(left)); //input is a rvalue
wrapper(left); //input is a lvalue
}
輸出是
T is a ref type(lvalue or rvalue ref)?:1
T is right value ref?:0
T is a ref type(lvalue or rvalue ref)?:0
T is right value ref?:0
T is a ref type(lvalue or rvalue ref)?:0
T is right value ref?:0
T is a ref type(lvalue or rvalue ref)?:1
T is right value ref?:0
結(jié)果跟我們上述的分析是一致的昭抒,T不會推導(dǎo)為右值引用,原因是輸入?yún)?shù)只有左值和右值兩種炼杖,不存在右值引用這種輸入灭返。
簡單分析一下前三個wrapper調(diào)用的區(qū)別,第一句調(diào)用right是個右值引用類型的變量坤邪,自然可以被取地址熙含,所以它是一個左值。
第二句調(diào)用毋庸置疑傳入的是一個右值艇纺。
第三句調(diào)用跟第二句的效果一模一樣婆芦,由于std::move返回了一個右值引用類型的返回值怕磨,函數(shù)返回值通常是個右值,所以這里它的返回值自然是一個右值消约。
最后幾點值得注意的
????再對比一下完美轉(zhuǎn)發(fā)和接受一個右值引用的函數(shù)對比肠鲫。
template <typename T>
void
wrapper(T&& value);
void
wrapper_none_template(int&& value);
????這兩個函數(shù)的區(qū)別是或粮,上面的模板函數(shù)即提現(xiàn)c++11所謂的完美轉(zhuǎn)發(fā)导饲,T會根據(jù)傳入實參參數(shù)的不同(左值、右值氯材、左值引用渣锦、常量左值引用)而推導(dǎo)出不同的類型。
下面的函數(shù)是一個只接受右值實參的函數(shù)氢哮,如果傳入的值不是右值袋毙,那么編譯會報錯。
????還有一點冗尤,用模板函數(shù)forwarding reference去進行轉(zhuǎn)發(fā)听盖,所有的傳入?yún)?shù)最終都是引用類型(左值的和右值的)即不產(chǎn)生值傳遞所造成的額外構(gòu)造和析構(gòu)。之所以提到這一點裂七,主要是針對傳入一個右值的情況皆看。前面提到如果傳入一個右值int,T會被推導(dǎo)為int背零,但是形參還是int&&腰吟,也就是說形參是一個右值引用,它綁定了一個右值徙瓶,所以還是一個引用毛雇。