唐新國 周天宏
摘要在C++系統(tǒng)中有靜態(tài)分派編程技術(shù)和動態(tài)分派編程技術(shù),將一些執(zhí)行期的分派提前至編譯期,可以減少了編譯后的代碼長度也提高了程序總體運(yùn)行速度.分析了引入靜態(tài)分派技術(shù)的必要性以及常用的靜態(tài)分派技術(shù),提出了通過使用Boost庫中的enable_if模板族來非侵入地(nonintrusively)顯示管理模板函數(shù)的重載集的方式來實(shí)現(xiàn)C++中靜態(tài)分派編程技術(shù),不但提出了一種新的靜態(tài)分派思路,更為重要的是通過這種方式可以根據(jù)模板函數(shù)的返回值來重載模板函數(shù).
關(guān)鍵詞靜態(tài)分派動態(tài)分派模板元編程模板特化
中圖分類號TP311文獻(xiàn)標(biāo)識碼A文章編號10002537(2015)05007006
A Static Dispatching Technique in C++ with
Explicitly Managing the Overload Set
TANG Xinguo1*, ZHOU Tianhong2
(1.Information Technology School, Hubei Polytechnic Institute, Xiaogan 432100, China;
2.Department of Information Engineering, Wuhan Business University, Wuhan 430056, China)
AbstractIn C++, there exists static dispatching technique and dynamic dispatching technique. Moving some dispatching at run time up to compiling time can reduce length of complied code and improve the overall running speed. The necessity of introducing static dispatching technique and those used commonly was analyzed, and then a way to realize static dispatching technique was presented by using enable_if template group in Boost library nonintrusively explicitly to manage the overloaded set of template functions. More importantly, in this way, one could overload template functions by their return values.
Key wordsstatic dispatching; dynamic dispatching; metaprogramming; template specialization
模板元編程(Metaprogramming)指的是高階編程[1],它運(yùn)行在編譯期.作為一種高階C++編程技術(shù),C++強(qiáng)大的模板機(jī)制賦予了模板在編譯期的運(yùn)算能力,模板元編程突出了編譯期在整個程序構(gòu)建和運(yùn)行過程中的地位,努力將計算從運(yùn)行期提前至編譯期,它既能有效地防止程序錯誤被傳播到運(yùn)行期,又能夠?qū)崿F(xiàn)以靜態(tài)代碼控制動態(tài)代碼的目標(biāo),使計算盡可能完成于編譯期的同時也提高了最終程序的運(yùn)行性能.
MPL[1](MetaProgramming Library)是由David Abrahams和Aleksey Gurtovoy為方便模板元編程而開發(fā)的庫,2003年被Boost吸納為其中的一員,此后又歷經(jīng)一些重大修改,目前已經(jīng)相當(dāng)完善.MPL的出現(xiàn)是C++模板元編程發(fā)展中的一大創(chuàng)舉,它提供了一個通用、高層次的編程框架,包括了序列、迭代器、算法、元函數(shù)等組件,具有高度的可重用性,提高了模板元編程的效率,使模板元編程的應(yīng)用范圍得到相當(dāng)?shù)臄U(kuò)展.
C++模板元編程誕生于十多年前,最初的研究方向是編譯期數(shù)值計算,后來的實(shí)踐發(fā)展證明,此項技術(shù)在類型計算領(lǐng)域也可以釋放出巨大能量.現(xiàn)在模板元編程主要用于:數(shù)據(jù)計算、解開循環(huán)、類型處理和自動代碼生成.
模板元編程技術(shù)有兩個強(qiáng)大的優(yōu)勢.首先它使得用其他方法很難或不可能的事情變得容易.第二因為 template metaprograms(模板元程序)在 C++ 編譯期間執(zhí)行,它們能將工作從運(yùn)行時轉(zhuǎn)移到編譯時.一個結(jié)果就是通常在運(yùn)行時才能被察覺的錯誤能夠在編譯期間被發(fā)現(xiàn).另一個結(jié)果是 C++ 程序使得 TMP 的使用在以下每一個方面都能更有效率:更小的可執(zhí)行代碼,更短的運(yùn)行時間,更少的內(nèi)存需求.
靜態(tài)分派技術(shù)就是通過一些基于編譯期計算出來的結(jié)果來選擇不同的運(yùn)行期行為或接口的程序設(shè)計方式,該技術(shù)能將一些執(zhí)行期的分派提前至編譯期,從而減少了編譯后的代碼長度,也提高了程序總體運(yùn)行速度.在C++的著名Loki庫中是通過一種將“數(shù)值轉(zhuǎn)換成型別”(Int2Type)[2]的技術(shù)來實(shí)現(xiàn)的.當(dāng)然在C++中實(shí)現(xiàn)靜態(tài)分派技術(shù)的方法很多,如:模板函數(shù)的重載、類模板特化等等,本文是通過使用Boost庫中的enable_if模板族來非侵入地(nonintrusively)顯示管理模板函數(shù)的重載集的方式來實(shí)現(xiàn)C++中靜態(tài)分派技術(shù),不但提出了一種新的靜態(tài)分派思路,更為重要的是通過這種方式可以根據(jù)模板函數(shù)的返回值來重載模板函數(shù).
湖南師范大學(xué)自然科學(xué)學(xué)報第38卷第5期唐新國等:基于顯示管理重載集的C++靜態(tài)分派研究1C++的靜態(tài)分派
1.1C++的靜態(tài)分派的介紹
在C++中可以通過一些基于編譯期計算出來的結(jié)果來選擇不同的運(yùn)行期行為或接口的程序設(shè)計方式稱為靜態(tài)分派(static dispatching).事實(shí)上在C++程序設(shè)計中經(jīng)常使用的是執(zhí)行期進(jìn)行分派(dispatching),執(zhí)行期進(jìn)行分派通常是使用ifelse或switch語句來實(shí)現(xiàn)的.大部分情況下其執(zhí)行期的成本是微不足道的,然而有時還是無法常常這么做,因為ifelse或switch語句要求每一個語句分支都要能夠得到編譯成功,即使該條件在編譯期就知道了.
例如設(shè)計一個泛型容器NiftyContainer,它將元素類型參數(shù)化[2]:
template〈class T〉
class NiftyContainer{};
由于NiftyContainer是泛型容器,那么它即可以包括類型為T的對象引用,也可以包括指向類型為T的對象指針.如果要復(fù)制泛型容器NiftyContainer中的某個元素,可以調(diào)用其copy構(gòu)造函數(shù)(針對nonpolymorphic類型)或調(diào)用其虛函數(shù)Clone()(針對polymorphic類型).具體設(shè)計如下:
template〈class T,bool isPolymorphic〉
class NiftyContainer{
…
void DoSomething(){
T*pSomeObj=…;
if(isPolymorphic){
T* pNewObj=pSomeObj〉Clone();
…polymorphic algorithm…(多態(tài)算法)
}else{
T* pNewObj=new T(*pSomeObj);//調(diào)用copy構(gòu)造函數(shù)
…nonpolymorphic algorithm…(非多態(tài)算法)
}
}
};
上述算法表面上看沒有問題,但實(shí)際上任何一款C++編譯器都不會使之僥幸編譯通過.因為編譯器會編譯每一個語句分支,如果多態(tài)算法使用pSomeObj〉Clone(),那么對任何一個沒有定義成員函數(shù)Clone()的類型,編譯器都會停留在語句pSomeObj〉Clone()處告之編譯通不過.當(dāng)然對nonpolymorphic類型也有可能編譯失敗,因為有些類型會將copy構(gòu)造函數(shù)置于private區(qū)域.
1.2C++的靜態(tài)分派的設(shè)計方式
在上面的執(zhí)行期分派算法中,如果可以讓編譯器不編譯那個不可能被執(zhí)行的代碼段為好,例如可以將運(yùn)行期測試換成相應(yīng)的編譯期測試,C++中靜態(tài)分派的設(shè)計方式通常是[3]:
template 〈class T〉
void f(T x){
if(boost::is_class〈T〉::value){
…implementation 1…
}else{
…implementation 2…
}
}
由于測試條件boost::is_class〈T〉::value可以完全地在編譯期決定,所以很多C++編譯器會對測試條件boost::is_class〈T〉::value進(jìn)行優(yōu)化,并僅為選擇了的if語句分支生成代碼.這種方式簡單清晰,只要它能工作,其概念上的開銷就非常小甚至沒有,實(shí)際上這種靜態(tài)分派的設(shè)計方法也并非普遍適應(yīng).
1.3C++靜態(tài)分派的不足
考慮上面的函數(shù)模板被實(shí)現(xiàn)為下面的樣子時會發(fā)生什么[4]:
template 〈class T〉
void f(T x){
if(boost::is_class〈T〉::value){
std::cout〈〈x::value;//處理整型常量外覆器
}else{
std::cout〈〈x; //處理非整型常量外覆器
}
}
這里的意圖是使函數(shù)模板f能夠輸出一個整數(shù)類型(如int)的值或者一個整型常量外覆器(如long_〈5〉)的值.然而函數(shù)調(diào)用f(42)就會得到一個編譯錯誤,問題仍然與執(zhí)行期進(jìn)行分派一樣,在于整個函數(shù)都需要進(jìn)行類型檢查,包括if語句的全部分支,但我們無法訪問整數(shù)類型int的根本不存在的::value成員,這同樣也出現(xiàn)了運(yùn)行期分派的缺點(diǎn).
采用模板函數(shù)的重載[11]、類模板特化[12]等方法可解決上述問題,關(guān)鍵是將每一個分支單獨(dú)寫成一個獨(dú)立的代碼段.采用顯示管理重載集的方法來解決C++的靜態(tài)分派問題.
2基于顯式管理重載集對C++靜態(tài)分派設(shè)計方式的改進(jìn)
2.1顯示管理重載集的原理
在C++中,常常使用CRTP[13](奇特的遞歸模板模式)來識別管理重載集,但有時CRTP對于限制一般化的函數(shù)模板實(shí)參的范圍是很不夠的.比如我們可能希望通過函數(shù)模板操作內(nèi)建類型(它們沒有基類)或者操作已有的第三方類型(不能修改它們).這時就可以使用Boost庫中的enable_if模板族[14]來非侵入地(nonintrusively)顯示管理模板函數(shù)的重載集,在編譯期決定一個參數(shù)類型的適當(dāng)性.
Boost庫中的enable_if模板族的工作原理為:
template〈bool,class T=void〉
struct enable_if_c
{
typedef T type;
};
template〈class T〉
struct enable_if_c〈false,T〉
{};
template〈class Cond,class T=void〉
struct enable_if
:enable_if_c〈Cond::value,T〉
{};
注意:當(dāng)C的值為false時,enable_if_c〈C,T〉::type不存在,根據(jù)C++標(biāo)準(zhǔn)的重載決議規(guī)則,當(dāng)一個函數(shù)模板的實(shí)參推導(dǎo)失敗時,它對被考慮準(zhǔn)備調(diào)用的候選函數(shù)集沒有貢獻(xiàn),并且不會導(dǎo)致一個錯誤.這個原則已經(jīng)被David Vandevoorde和Nicolai Josuttis賦予“替換失敗并非錯誤(Substitution Failure Not An Error,SFINAE)”的名號[15].
2.2C++靜態(tài)分派設(shè)計方式的改進(jìn)
重載集有時也能進(jìn)行靜態(tài)分派,如果能在編譯期就能決定一個參數(shù)類型的適當(dāng)性,則可用Boost的enable_if模板非侵入地(nonintrusively)顯式管理重載集,從而達(dá)到靜態(tài)分派的目的.用C++的顯示管理重載集方式來實(shí)現(xiàn)靜態(tài)分派問題.首先對模板函數(shù)
template 〈class T〉
void f(T x){
if(boost::is_class〈T〉::value){
std::cout〈〈x::value;//處理整型常量外覆器
}else{
std::cout〈〈x; //處理非整型常量外覆器
}
}
進(jìn)行改進(jìn).重新設(shè)計后的代碼為:
template〈class T〉
typename boost::enable_if〈
typename boost::is_class〈T〉::type
〉::type
print(T x){
std::cout〈〈T::value〈〈std::endl;
}
template〈class T〉
typename boost::enable_if〈
typename mpl::not_〈boost::is_class〈T〉 〉::type
〉::type
print(T x){
std::cout〈〈x〈〈std::endl;
}
這樣print(42)和print(mpl::int_〈56〉())將訪問不同的模板函數(shù),只有匹配的那個模板函數(shù)才能實(shí)例化且不會出現(xiàn)編譯錯誤.
其次應(yīng)用C++的顯示管理重載集方式來重新實(shí)現(xiàn)C++標(biāo)準(zhǔn)程序庫中的advance泛型算法.advance泛型算法[5]就是一個使用標(biāo)簽分派的極好例子,advance用來將一個迭代器i移動n個位置,對不同類型的迭代器i實(shí)現(xiàn)的策略是不同的.如果i是隨機(jī)迭代器,則可用i+=n來實(shí)現(xiàn).如果i是雙向迭代器,可以在運(yùn)行期決定是否遞增或遞減迭代器;如果i是前向迭代器,則只能是向前步進(jìn)多步.通過編譯期的標(biāo)簽分派也可能達(dá)到靜態(tài)分派,它的基本思想是從運(yùn)行期的泛型編程借用而來.一個標(biāo)簽(tag)僅僅是一個空類[6](empty class),其唯一的用途就是在編譯期傳達(dá)信息.那么改進(jìn)后的代碼為:
template〈class Iterator,class Distance〉
typename boost::enable_if〈
typename boost::is_same〈std::input_iterator_tag,//i是前向迭代器
typename std::iterator_traits〈Iterator〉::iterator_category〉::type
〉::type
advance(Iterator& i,Distance n){
while(n--)++i;
}
template〈class Iterator,class Distance〉
typename boost::enable_if〈
typename boost::is_same〈std::bidirectional_iterator_tag,// i是雙向迭代器
typename std::iterator_traits〈Iterator〉::iterator_category〉::type
〉::type
advance(Iterator& i,Distance n){
if(n〉=0)
while(n--)++i;
else
while(n++)--i;
}
template〈class Iterator,class Distance〉
typename boost::enable_if〈
typename boost::is_same〈std::random_access_iterator_tag,// i是隨機(jī)迭代器
typename std::iterator_traits〈Iterator〉::iterator_category〉::type
〉::type
advance(Iterator& i,Distance n){
i+=n;
}
對于::advance(i,4)調(diào)用,同樣會根據(jù)迭代器i的類型來選擇一個合適的模板函數(shù),達(dá)到迭代器i的遞增,其他可能使用一個給定的迭代器所未實(shí)現(xiàn)的重載則永遠(yuǎn)不會被實(shí)例化.從以上實(shí)例過程來看:通過C++的顯示管理重載集方式來實(shí)現(xiàn)靜態(tài)分派問題就是將每一個可能的分支轉(zhuǎn)換成一個重載的模板函數(shù).
2.3應(yīng)用拓展
利用Boost庫中的enable_if模板族來非侵入地(nonintrusively)顯示管理模板函數(shù)的重載集實(shí)現(xiàn)C++的靜態(tài)分派問題時,同時也是根據(jù)模板函數(shù)的返回值來實(shí)現(xiàn)模板函數(shù)重載的過程.
下面的函數(shù)模板僅應(yīng)用于算術(shù)類型的迭代器上并將容器中所有元素之和求出,通過元函數(shù)[7]boost::iterator_value來獲取一個迭代器的value_type.
template〈class Iterator〉
typename boost::enable_if〈
boost::is_arithmetic〈//啟用條件
typename boost::iterator_value〈Iterator〉::type
〉
,typename
boost::iterator_value〈Iterator〉::type//返回類型
〉::type
sum(Iterator start,Iterator end){
typename boost::iterator_value〈Iterator〉::type x(0);
for(;start!=end;++start)
x+=*start;
return x;
}
如果啟用條件C的::value為true,enable_if〈C,T〉::type將為T,于是sum將返回一個“Iterator的value_type”的對象.否則sum將從重載決議過程中消失[8].
當(dāng)有模板函數(shù)重載發(fā)揮作用時,這項技術(shù)就變得真正有趣起來.因為上述模板sum已經(jīng)根據(jù)它返回值被限制為(接收)適當(dāng)?shù)膮?shù),現(xiàn)在可以添加另一個重載,它允許我們計算出vector〈vector〈int〉〉的所有算術(shù)元素,以及其他算術(shù)類型的嵌套的容器.
template〈class Iterator〉
struct inner_value
:boost::iterator_value〈
typename boost::iterator_value〈Iterator〉::type::iterator
〉{};
template〈class Iterator〉
typename boost::lazy_disable_if〈
boost::is_arithmetic〈//禁用條件
typename boost::iterator_value〈Iterator〉::type
〉
,inner_value〈Iterator〉//結(jié)果無函數(shù)
〉::type
sum(Iterator start,Iterator end){
typename inner_value〈Iterator〉::type x(0);
for(;start!=end;++start)
x+=sum(start〉begin(),start〉end());
return x;
}
Lazy_disable_if的“disable”指出當(dāng)條件被滿足時,該函數(shù)被從重載集中移走了,“l(fā)azy”則意味著函數(shù)的結(jié)果::type是以一個無參元函數(shù)調(diào)用第二個參數(shù)的結(jié)果[9].
注意,只有當(dāng)?shù)鞯闹殿愋褪橇硪粋€迭代器時,inner_value〈Iterator〉才能被調(diào)用,這時sum(Iterator start,Iterator end)會調(diào)用第二個模板函數(shù).當(dāng)?shù)鞯闹殿愋褪撬阈g(shù)類型時,sum(Iterator start,Iterator end)會調(diào)用第一個模板函數(shù).對于其他類型的實(shí)參,當(dāng)發(fā)生實(shí)參推導(dǎo)失敗時,那么在重載決議期間它對考慮準(zhǔn)備調(diào)用的候選函數(shù)集沒有貢獻(xiàn)[10],這時并不會發(fā)生錯誤,這個過程當(dāng)然也可以看成是根據(jù)模板函數(shù)的返回值來實(shí)現(xiàn)模板函數(shù)重載的過程.
3結(jié)束語
模板元編程是C++中一種高級編程技術(shù),它處于編譯期,靜態(tài)分派技術(shù)是基于編譯期計算出來的結(jié)果來選擇不同的運(yùn)行期行為或接口的程序設(shè)計方式,這種技術(shù)能將一些執(zhí)行期的分派提前至編譯期,從而減少了編譯后的代碼長度,也提高了程序總體運(yùn)行速度.這里則是通過使用Boost庫中的enable_if模板族來非侵入地(nonintrusively)顯示管理模板函數(shù)的重載集的方式來實(shí)現(xiàn)C++中靜態(tài)分派技術(shù),提出了一種新的靜態(tài)分派思路,通過這種方式可以根據(jù)模板函數(shù)的返回值來重載模板函數(shù).
參考文獻(xiàn):
[1]DAVID A.C++模板元編程[M].榮耀,譯.北京:機(jī)械工業(yè)出版社,2010.
[2]ANDREI A.C++設(shè)計新思維[M].侯捷,於春景,譯.武漢:華中科技大學(xué)出版社,2003.
[3]DAVID V, NICOLAI M J. C++ template中文版[M].陳偉柱,譯.北京:人民郵電出版社,2004.
[4]HERBERT S.C++完全參考手冊[M].4版.北京:清華大學(xué)出版社,2007.
[5]王曉宇,錢紅兵.基于UML類圖和順序圖的C++代碼自動生成方法的研究[J].計算機(jī)應(yīng)用與軟件, 2013,30(1):190195.
[6]周毅,顧進(jìn)廣,張曉龍,等.一種面向復(fù)合屬性的自適應(yīng)對象模型[J].計算機(jī)應(yīng)用與軟件, 2008,25(11):137139.
[7]徐靜雯,周繼恩,施躍躍,等.軟件密集型系統(tǒng)的故障診斷技術(shù)研究[J].計算機(jī)應(yīng)用與軟件, 2012,29(2):175178.
[8]黃山,陳昱松,王建偉,等.一種基于UML與SDL融合建模的組件系統(tǒng)測試方法[J].計算機(jī)應(yīng)用與軟件, 2011,28(7):175177,182.
[9]唐峰,許第洪.SolidWorks與Pro/Engineer之間圖形數(shù)據(jù)交換方式的研究[J].湖南師范大學(xué)自然科學(xué)學(xué)報, 2011,34(1):3742.
[10]劉震,繆力.基于動態(tài)調(diào)用圖的Java程序修改影響分析技術(shù)[J].湖南師范大學(xué)自然科學(xué)學(xué)報, 2011,34(6):2630.
[11]PLAUGER P J, ALEXANDER A S.C++ STL中文版[M].王昕,譯.北京:中國電力出版社,2002.
[12]JASMIN B, MARK S.C++ GUI Qt 4編程[M].閆鋒欣,譯.北京:電子工業(yè)出版社,2008.
[13]葉至軍.C++ STL開發(fā)技術(shù)導(dǎo)引[M].北京:人民郵電出版社, 2007.
[14]MATTHEW H A. 泛型編程與STL[M].侯捷,譯.北京:中國電力出版社,2003.
[15]ANDREW K, BARBARA M. C++沉思錄[M].黃曉春,譯.北京:人民郵電出版社,2008.
(編輯陳笑梅)