c++可變參數(shù)模板使用示例源碼解析
前言
我們知道,C++模板能力很強(qiáng)大,比起Java泛型這種語法糖來說,簡直就是降維打擊。而其中,可變參數(shù)模板,就是其中一個非常重要的特性。那什么是可變參數(shù)模板,以及為什么我們需要他?
首先我們考慮一個經(jīng)典的場景:
我們需要編寫一個函數(shù),來打印變量信息。
比如:
int code = 1; string msg = "success"; printMsg(code,msg); // 輸出: 1,success
而我們需要打印的參數(shù)信息是不確定的,也有可能是下面的情況:
float value = 0.8f; printMsg(code,msg,"main"); // 輸出: 1,success,main printMsg(value,code); // 輸出: 0.8,1
printMsg
的參數(shù)類型、數(shù)量都是不確定的,無論是普通模板、還是使用容器,都無法完成這個任務(wù)。而可變參數(shù)模板,可以非常完美完成這個任務(wù)。
可變參數(shù)模板,意為該模板的類型與數(shù)量都是不確定,能夠接收任意的參數(shù)匹配,造就了其極高的靈活度。
認(rèn)識可變模板參數(shù)
template<typename T,typename... Args> void printMsg(T t, Args... args) {}
上述代碼為可變參數(shù)模板的例子。首先要了解一個概念:模板參數(shù)包,函數(shù)參數(shù)包。
typename...
表示一個模板參數(shù)包類型,在typename后跟了三個點 ,Args
是一個模板參數(shù)包,他可以是0或多種類型的組合。Args...
,表示將這個參數(shù)包展開,作為函數(shù)的形參,args
也稱為函數(shù)參數(shù)包
舉個例子:
// T的類型是 int // Args的類型是 int、float、string 組成的模板參數(shù)包 printMsg(1,2,0.8f,"success"); // 模板會被實例化為此函數(shù)原型 void printMsg(int,int,float,string);
對于參數(shù)包,我們可以使用sizeof...
來獲取該參數(shù)包中有多少個類型。如sizeof...(args); or sizeof...(Args);
。
那么,對于這個可變模板參數(shù)類型,我們要如何使用它呢?
使用可變模板參數(shù)
遞歸法
遞歸法利用的是類型匹配原理,將參數(shù)包中的參數(shù),一個個給他分離出來。我們從一個實際的例子來理解他。假如我們要實現(xiàn)前言章節(jié)中的printMsg
函數(shù),那么他的實現(xiàn)代碼如下:
template<typename T,typename ...Args> void printMsg(const T& t, const Args&... args) { std::cout << t << ", "; printMsg(args...); } // 調(diào)用 printMsg(1,0.3f,"success");
當(dāng)我們調(diào)用printMsg(1,0.3f,"success")
代碼時,模板函數(shù)被實例化為:
template<int,float,string> void printMsg(const int& t, const float& arg1, const string& arg2) { std::cout << t << ", "; printMsg(arg1, arg2); }
代碼中再次遞歸調(diào)用了printMsg
,模板函數(shù)被實例化為:
template<float,string> void printMsg( const float& arg1, const string& arg2) { std::cout << t << ", "; printMsg(arg2); }
發(fā)現(xiàn)規(guī)律了嗎?當(dāng)我們不斷遞歸調(diào)用printMsg
時,參數(shù)報Args
會被一層層解開,并將類型匹配到模板T
上,從而將參數(shù)包Args
中的參數(shù)逐一處理。
與此同時,我們也知道一個關(guān)鍵點:遞歸需要有終止條件。因此,我們需要在只剩下一個參數(shù)的時候?qū)⑵浣K結(jié):
template<typename T> void printMsg(const T& t) { std::cout << t << std::endl; }
c++在匹配模板時,會優(yōu)先匹配非可變參數(shù)模板,因此非可變參數(shù)模板則成為了遞歸的終止條件。這樣我們就實現(xiàn)了一個函數(shù),能夠接受任意數(shù)量、任意類型(支持<<運算符)的參數(shù)。
特例化
遞歸法是最為常見的使用可變參數(shù)模板的方式。對于參數(shù)包來說,除了遞歸法,其次就為特例化。舉個例子,還是我們上面的printMsg
函數(shù):
template<> void printMsg(const int& errorCode,const float& strength,const double& value) { std::cout << "errorCode:" << errorCode << " strength:" << strength << " value:" << value << std::endl; } printMsg(1,0.8f,0.8);
針對<int,float,double>類型的模板做了一個特例化,則在我們調(diào)用此類型的模板時,會優(yōu)先匹配特例化。這也是一種處理可變模板參數(shù)的方式。
除此之外,還有很多對于可變模板參數(shù)的神奇用法,進(jìn)一步提高他的靈活性。
包拓展
這里包,指的是函數(shù)參數(shù)包以及可變模板參數(shù)包。前面的例子中已經(jīng)存在兩個包拓展,但更多的是屬于可變參數(shù)模板的語法層面,所以并沒有展開說。比如上面我們提到的代碼:
template<typename T,typename ...Args> void printMsg(const T& t, const Args&... args) { std::cout << t << ", "; printMsg(args...); } printMsg(1,0.8f,0.8);
這里有兩個包拓展:
- 函數(shù)的形參,在
Args&
之后跟了三個點,表示將Args
參數(shù)包展開,例子中展開后的函數(shù)原型是void printMsg(const int&,const float&,const double&);
- 第二處展開是在遞歸調(diào)用時,將函數(shù)參數(shù)包形參展開
args...
,例子中展開后為printMsg(0.8f,0.8);
。
在涉及到函數(shù)調(diào)用、函數(shù)聲明時,都需要用到上面這兩個包拓展語法。但我們會發(fā)現(xiàn)并沒有什么可以操作的空間,他更多就是一個可變模板函數(shù)的固定語法。但除此之外,包拓展可以有一個更加神奇的操作。
還是上面的例子,但是這里我們需要對打印的數(shù)據(jù)進(jìn)行一輪過濾,對int數(shù)據(jù)超過99、float數(shù)據(jù)超過0.9進(jìn)行預(yù)警報告,其他數(shù)據(jù)不做處理。那么這個怎么處理呢?
理論上說,我們需要對每個參數(shù)包中的每個數(shù)據(jù)進(jìn)行處理,那我們可以在遞歸中,判斷T的類型,再根據(jù)不同的類型進(jìn)行處理。這種方式是可行的,但c++提供了更加好用的另一種方式??聪旅娴拇a:
template<typename T> const T& filterParam(const T& t) { return t; } template<> const int& fileterParam(const int& t) { if (t > 99) { onWarnReport(); } return t; } template<> const float& fileterParam(const float& t) { if (float > 0.9) { onWarnReport(); } return t; } template<typename... Args> void printMsgPlug(const Args&... args) { printMsg(filterParam(args)...); //關(guān)鍵代碼 } printMsgPlus(1,0,3f,1.8f);
可以看到我們的關(guān)鍵代碼在于printMsg(filterParam(args)...);
這一行,他等價于printMsg(filterParam(1),filterParam(0.3f) ,filterParam(1.8f));
三個小點移動到了函數(shù)調(diào)用的后面,即可以實現(xiàn)這樣的效果。
這種方式的優(yōu)點在于,他可以將過濾相關(guān)的邏輯,抽離到另外一個函數(shù)中去單獨處理,利用模板的特性對數(shù)據(jù)進(jìn)行統(tǒng)一或者單獨處理。而且,使用typeId判斷類型的方式并不總是可靠的,這種方式會更加穩(wěn)定。
此外,針對雙重過濾的方式,包拓展的解決方案也會更加優(yōu)雅。假如,我們在打印數(shù)據(jù)之前,需要對數(shù)據(jù)進(jìn)行一次轉(zhuǎn)換,之后再對轉(zhuǎn)換結(jié)果進(jìn)行過濾判斷是否需要預(yù)警報告。那么我們的偽代碼可以是如下:
template<typename T> T filterParam(const T& t) { T result = convertParam(t); if()... return result; } template<typename T> T convertParam(const T& t) {...} template<typename... Args> void printMsgPlug(const Args&... args) { printMsg(filterParam(args)...); //關(guān)鍵代碼 }
而如果使用遞歸結(jié)合typeid的方式,可能就需要更多個switch進(jìn)行類型匹配嵌套解決,且其結(jié)果總是不可靠的。
最后,并不是所有可變模板函數(shù),都能使用遞歸去解決問題。例如我們需要一個能夠構(gòu)建unique_ptr的函數(shù),他的簡化版可以是這樣的:
template<typename T, typename... Args> std::unique_ptr<T> make_unique(Args&... args) { return std::unique_ptr<T>(new T(fileterParam(args)...)); }
這個寫法是不夠完善的,但是方便我們理解。這個時候,如果我們需要對參數(shù)進(jìn)行過濾,那么遞歸的方式,就無法在這里使用了,而必須使用包拓展。
完美轉(zhuǎn)發(fā)
完美轉(zhuǎn)發(fā)在可變模板中非常常見,他的作用在于保持原始的數(shù)據(jù)類型。參考我們上面的make_unique
函數(shù),在移除fileterParam函數(shù)之后,,我們希望,傳給make_unique
函數(shù)的數(shù)據(jù),能夠原封不動地,傳遞給T
的構(gòu)造函數(shù)。那么他的實現(xiàn)如下:
template<typename T, typename... Args> std::unique_ptr<T> make_unique(Args&&... args) { return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); }
Args&&
表示通用引用,他能接收左值引用,也可以接收右值引用。std::forward
表示保持參數(shù)的原始類型。因為我們知道,右值引用本身是左值,所以我們需要將其轉(zhuǎn)為右值傳遞給構(gòu)造函數(shù)。
這樣,我們就能夠原封不動地將數(shù)據(jù)傳遞給構(gòu)造函數(shù),而不修改數(shù)據(jù)類型。這部分類型屬于右值與引用的范疇,這里不詳細(xì)展開解析。
但是對于可變模板來說,這里有一個關(guān)鍵需要注意一下:通用引用的本身,是 引用類型。假如我們傳遞了一個int
類型進(jìn)來,那么轉(zhuǎn)化之后就變成了int&
。此時如果我們使用Args
類型去做模板匹配,很容易發(fā)生匹配失敗的問題,會提示int&
無法匹配到int
類型,需要多加注意一下。要解決這個問題也比較簡單,將其引用類型移除即可。在c++11中,可以使用以下代碼移除所有的修飾與引用,保持基礎(chǔ)的數(shù)據(jù)類型:
template<typename T> using remove_cvRef = typename std::remove_cv<typename std::remove_reference<T>::type>::type; std::vector<decltype(remove_cvRef<T>)> v;
在匹配模板的時候,可以使用decltype來獲取移除后的類型進(jìn)行匹配。
總結(jié)
可變參數(shù)模板在實際的使用中,更多還是結(jié)合完美轉(zhuǎn)發(fā)來使用,實現(xiàn)對象的統(tǒng)一構(gòu)造或者接口調(diào)用封裝等??勺儏?shù)的存在,使得模板接口的靈活度提升了一個檔次,如果你在實際開發(fā)中遇到類似的需求,不妨使用一下,會給你帶來驚喜的。
以上就是c++可變參數(shù)模板使用示例源碼解析的詳細(xì)內(nèi)容,更多關(guān)于c++可變參數(shù)模板的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C++的template模板中class與typename關(guān)鍵字的區(qū)別分析
這篇文章中我們來談一談C++的template模板中class與typename關(guān)鍵字的區(qū)別分析,同時會講到嵌套從屬名稱時的一些注意點,需要的朋友可以參考下2016-06-06C++默認(rèn)參數(shù)與函數(shù)重載及注意事項
這篇文章主要介紹了C++默認(rèn)參數(shù)與函數(shù)重載及注意事項包括語法與使用,通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值 ,需要的朋友可以參考下2020-03-03如何通過wrap malloc定位C/C++的內(nèi)存泄漏問題
用C/C++開發(fā)的程序執(zhí)行效率很高,但卻經(jīng)常受到內(nèi)存泄漏的困擾。本文提供一種通過wrap malloc查找memory leak的思路。2021-05-05