<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
我們知道,C++模板能力很強大,比起Java泛型這種語法糖來說,簡直就是降維打擊。而其中,可變引數模板,就是其中一個非常重要的特性。那什麼是可變引數模板,以及為什麼我們需要他?
首先我們考慮一個經典的場景:
我們需要編寫一個函數,來列印變數資訊。
比如:
int code = 1; string msg = "success"; printMsg(code,msg); // 輸出: 1,success
而我們需要列印的引數資訊是不確定的,也有可能是下面的情況:
float value = 0.8f; printMsg(code,msg,"main"); // 輸出: 1,success,main printMsg(value,code); // 輸出: 0.8,1
printMsg
的引數型別、數量都是不確定的,無論是普通模板、還是使用容器,都無法完成這個任務。而可變引數模板,可以非常完美完成這個任務。
可變引數模板,意為該模板的型別與數量都是不確定,能夠接收任意的引數匹配,造就了其極高的靈活度。
template<typename T,typename... Args> void printMsg(T t, Args... args) {}
上述程式碼為可變引數模板的例子。首先要了解一個概念:模板引數包,函數引數包。
typename...
表示一個模板引數包型別,在typename後跟了三個點 ,Args
是一個模板引數包,他可以是0或多種型別的組合。Args...
,表示將這個引數包展開,作為函數的形參,args
也稱為函數引數包
舉個例子:
// T的型別是 int // Args的型別是 int、float、string 組成的模板引數包 printMsg(1,2,0.8f,"success"); // 模板會被範例化為此函數原型 void printMsg(int,int,float,string);
對於引數包,我們可以使用sizeof...
來獲取該引數包中有多少個型別。如sizeof...(args); or sizeof...(Args);
。
那麼,對於這個可變模板引數型別,我們要如何使用它呢?
遞迴法利用的是型別匹配原理,將引數包中的引數,一個個給他分離出來。我們從一個實際的例子來理解他。假如我們要實現前言章節中的printMsg
函數,那麼他的實現程式碼如下:
template<typename T,typename ...Args> void printMsg(const T& t, const Args&... args) { std::cout << t << ", "; printMsg(args...); } // 呼叫 printMsg(1,0.3f,"success");
當我們呼叫printMsg(1,0.3f,"success")
程式碼時,模板函數被範例化為:
template<int,float,string> void printMsg(const int& t, const float& arg1, const string& arg2) { std::cout << t << ", "; printMsg(arg1, arg2); }
程式碼中再次遞迴呼叫了printMsg
,模板函數被範例化為:
template<float,string> void printMsg( const float& arg1, const string& arg2) { std::cout << t << ", "; printMsg(arg2); }
發現規律了嗎?當我們不斷遞迴呼叫printMsg
時,引數報Args
會被一層層解開,並將型別匹配到模板T
上,從而將引數包Args
中的引數逐一處理。
與此同時,我們也知道一個關鍵點:遞迴需要有終止條件。因此,我們需要在只剩下一個引數的時候將其終結:
template<typename T> void printMsg(const T& t) { std::cout << t << std::endl; }
c++在匹配模板時,會優先匹配非可變引數模板,因此非可變引數模板則成為了遞迴的終止條件。這樣我們就實現了一個函數,能夠接受任意數量、任意型別(支援<<運運算元)的引數。
遞迴法是最為常見的使用可變引數模板的方式。對於引數包來說,除了遞迴法,其次就為特例化。舉個例子,還是我們上面的printMsg
函數:
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>型別的模板做了一個特例化,則在我們呼叫此型別的模板時,會優先匹配特例化。這也是一種處理可變模板引數的方式。
除此之外,還有很多對於可變模板引數的神奇用法,進一步提高他的靈活性。
這裡包,指的是函數引數包以及可變模板引數包。前面的例子中已經存在兩個包拓展,但更多的是屬於可變引數模板的語法層面,所以並沒有展開說。比如上面我們提到的程式碼:
template<typename T,typename ...Args> void printMsg(const T& t, const Args&... args) { std::cout << t << ", "; printMsg(args...); } printMsg(1,0.8f,0.8);
這裡有兩個包拓展:
Args&
之後跟了三個點,表示將Args
引數包展開,例子中展開後的函數原型是void printMsg(const int&,const float&,const double&);
args...
,例子中展開後為printMsg(0.8f,0.8);
。在涉及到函數呼叫、函數宣告時,都需要用到上面這兩個包拓展語法。但我們會發現並沒有什麼可以操作的空間,他更多就是一個可變模板函數的固定語法。但除此之外,包拓展可以有一個更加神奇的操作。
還是上面的例子,但是這裡我們需要對列印的資料進行一輪過濾,對int資料超過99、float資料超過0.9進行預警報告,其他資料不做處理。那麼這個怎麼處理呢?
理論上說,我們需要對每個引數包中的每個資料進行處理,那我們可以在遞迴中,判斷T的型別,再根據不同的型別進行處理。這種方式是可行的,但c++提供了更加好用的另一種方式。看下面的程式碼:
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)...); //關鍵程式碼 } printMsgPlus(1,0,3f,1.8f);
可以看到我們的關鍵程式碼在於printMsg(filterParam(args)...);
這一行,他等價於printMsg(filterParam(1),filterParam(0.3f) ,filterParam(1.8f));
三個小點移動到了函數呼叫的後面,即可以實現這樣的效果。
這種方式的優點在於,他可以將過濾相關的邏輯,抽離到另外一個函數中去單獨處理,利用模板的特性對資料進行統一或者單獨處理。而且,使用typeId判斷型別的方式並不總是可靠的,這種方式會更加穩定。
此外,針對雙重過濾的方式,包拓展的解決方案也會更加優雅。假如,我們在列印資料之前,需要對資料進行一次轉換,之後再對轉換結果進行過濾判斷是否需要預警報告。那麼我們的虛擬碼可以是如下:
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)...); //關鍵程式碼 }
而如果使用遞迴結合typeid的方式,可能就需要更多個switch進行型別匹配巢狀解決,且其結果總是不可靠的。
最後,並不是所有可變模板函數,都能使用遞回去解決問題。例如我們需要一個能夠構建unique_ptr的函數,他的簡化版可以是這樣的:
template<typename T, typename... Args> std::unique_ptr<T> make_unique(Args&... args) { return std::unique_ptr<T>(new T(fileterParam(args)...)); }
這個寫法是不夠完善的,但是方便我們理解。這個時候,如果我們需要對引數進行過濾,那麼遞迴的方式,就無法在這裡使用了,而必須使用包拓展。
完美轉發在可變模板中非常常見,他的作用在於保持原始的資料型別。參考我們上面的make_unique
函數,在移除fileterParam函數之後,,我們希望,傳給make_unique
函數的資料,能夠原封不動地,傳遞給T
的建構函式。那麼他的實現如下:
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
表示保持引數的原始型別。因為我們知道,右值參照本身是左值,所以我們需要將其轉為右值傳遞給建構函式。這樣,我們就能夠原封不動地將資料傳遞給建構函式,而不修改資料型別。這部分型別屬於右值與參照的範疇,這裡不詳細展開解析。
但是對於可變模板來說,這裡有一個關鍵需要注意一下:通用參照的本身,是 參照型別。假如我們傳遞了一個int
型別進來,那麼轉化之後就變成了int&
。此時如果我們使用Args
型別去做模板匹配,很容易發生匹配失敗的問題,會提示int&
無法匹配到int
型別,需要多加註意一下。要解決這個問題也比較簡單,將其參照型別移除即可。在c++11中,可以使用以下程式碼移除所有的修飾與參照,保持基礎的資料型別:
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來獲取移除後的型別進行匹配。
可變引數模板在實際的使用中,更多還是結合完美轉發來使用,實現物件的統一構造或者介面呼叫封裝等。可變引數的存在,使得模板介面的靈活度提升了一個檔次,如果你在實際開發中遇到類似的需求,不妨使用一下,會給你帶來驚喜的。
以上就是c++可變引數模板使用範例原始碼解析的詳細內容,更多關於c++可變引數模板的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45