?

基于顯示管理重載集的C++靜態分派研究

2015-10-20 11:26唐新國周天宏
關鍵詞:調用靜態模板

唐新國 周天宏

摘要在C++系統中有靜態分派編程技術和動態分派編程技術,將一些執行期的分派提前至編譯期,可以減少了編譯后的代碼長度也提高了程序總體運行速度.分析了引入靜態分派技術的必要性以及常用的靜態分派技術,提出了通過使用Boost庫中的enable_if模板族來非侵入地(nonintrusively)顯示管理模板函數的重載集的方式來實現C++中靜態分派編程技術,不但提出了一種新的靜態分派思路,更為重要的是通過這種方式可以根據模板函數的返回值來重載模板函數.

關鍵詞靜態分派動態分派模板元編程模板特化

中圖分類號TP311文獻標識碼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],它運行在編譯期.作為一種高階C++編程技術,C++強大的模板機制賦予了模板在編譯期的運算能力,模板元編程突出了編譯期在整個程序構建和運行過程中的地位,努力將計算從運行期提前至編譯期,它既能有效地防止程序錯誤被傳播到運行期,又能夠實現以靜態代碼控制動態代碼的目標,使計算盡可能完成于編譯期的同時也提高了最終程序的運行性能.

MPL[1](MetaProgramming Library)是由David Abrahams和Aleksey Gurtovoy為方便模板元編程而開發的庫,2003年被Boost吸納為其中的一員,此后又歷經一些重大修改,目前已經相當完善.MPL的出現是C++模板元編程發展中的一大創舉,它提供了一個通用、高層次的編程框架,包括了序列、迭代器、算法、元函數等組件,具有高度的可重用性,提高了模板元編程的效率,使模板元編程的應用范圍得到相當的擴展.

C++模板元編程誕生于十多年前,最初的研究方向是編譯期數值計算,后來的實踐發展證明,此項技術在類型計算領域也可以釋放出巨大能量.現在模板元編程主要用于:數據計算、解開循環、類型處理和自動代碼生成.

模板元編程技術有兩個強大的優勢.首先它使得用其他方法很難或不可能的事情變得容易.第二因為 template metaprograms(模板元程序)在 C++ 編譯期間執行,它們能將工作從運行時轉移到編譯時.一個結果就是通常在運行時才能被察覺的錯誤能夠在編譯期間被發現.另一個結果是 C++ 程序使得 TMP 的使用在以下每一個方面都能更有效率:更小的可執行代碼,更短的運行時間,更少的內存需求.

靜態分派技術就是通過一些基于編譯期計算出來的結果來選擇不同的運行期行為或接口的程序設計方式,該技術能將一些執行期的分派提前至編譯期,從而減少了編譯后的代碼長度,也提高了程序總體運行速度.在C++的著名Loki庫中是通過一種將“數值轉換成型別”(Int2Type)[2]的技術來實現的.當然在C++中實現靜態分派技術的方法很多,如:模板函數的重載、類模板特化等等,本文是通過使用Boost庫中的enable_if模板族來非侵入地(nonintrusively)顯示管理模板函數的重載集的方式來實現C++中靜態分派技術,不但提出了一種新的靜態分派思路,更為重要的是通過這種方式可以根據模板函數的返回值來重載模板函數.

湖南師范大學自然科學學報第38卷第5期唐新國等:基于顯示管理重載集的C++靜態分派研究1C++的靜態分派

1.1C++的靜態分派的介紹

在C++中可以通過一些基于編譯期計算出來的結果來選擇不同的運行期行為或接口的程序設計方式稱為靜態分派(static dispatching).事實上在C++程序設計中經常使用的是執行期進行分派(dispatching),執行期進行分派通常是使用ifelse或switch語句來實現的.大部分情況下其執行期的成本是微不足道的,然而有時還是無法常常這么做,因為ifelse或switch語句要求每一個語句分支都要能夠得到編譯成功,即使該條件在編譯期就知道了.

例如設計一個泛型容器NiftyContainer,它將元素類型參數化[2]:

template〈class T〉

class NiftyContainer{};

由于NiftyContainer是泛型容器,那么它即可以包括類型為T的對象引用,也可以包括指向類型為T的對象指針.如果要復制泛型容器NiftyContainer中的某個元素,可以調用其copy構造函數(針對nonpolymorphic類型)或調用其虛函數Clone()(針對polymorphic類型).具體設計如下:

template〈class T,bool isPolymorphic〉

class NiftyContainer{

void DoSomething(){

T*pSomeObj=…;

if(isPolymorphic){

T* pNewObj=pSomeObj〉Clone();

…polymorphic algorithm…(多態算法)

}else{

T* pNewObj=new T(*pSomeObj);//調用copy構造函數

…nonpolymorphic algorithm…(非多態算法)

}

}

};

上述算法表面上看沒有問題,但實際上任何一款C++編譯器都不會使之僥幸編譯通過.因為編譯器會編譯每一個語句分支,如果多態算法使用pSomeObj〉Clone(),那么對任何一個沒有定義成員函數Clone()的類型,編譯器都會停留在語句pSomeObj〉Clone()處告之編譯通不過.當然對nonpolymorphic類型也有可能編譯失敗,因為有些類型會將copy構造函數置于private區域.

1.2C++的靜態分派的設計方式

在上面的執行期分派算法中,如果可以讓編譯器不編譯那個不可能被執行的代碼段為好,例如可以將運行期測試換成相應的編譯期測試,C++中靜態分派的設計方式通常是[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進行優化,并僅為選擇了的if語句分支生成代碼.這種方式簡單清晰,只要它能工作,其概念上的開銷就非常小甚至沒有,實際上這種靜態分派的設計方法也并非普遍適應.

1.3C++靜態分派的不足

考慮上面的函數模板被實現為下面的樣子時會發生什么[4]:

template 〈class T〉

void f(T x){

if(boost::is_class〈T〉::value){

std::cout〈〈x::value;//處理整型常量外覆器

}else{

std::cout〈〈x; //處理非整型常量外覆器

}

}

這里的意圖是使函數模板f能夠輸出一個整數類型(如int)的值或者一個整型常量外覆器(如long_〈5〉)的值.然而函數調用f(42)就會得到一個編譯錯誤,問題仍然與執行期進行分派一樣,在于整個函數都需要進行類型檢查,包括if語句的全部分支,但我們無法訪問整數類型int的根本不存在的::value成員,這同樣也出現了運行期分派的缺點.

采用模板函數的重載[11]、類模板特化[12]等方法可解決上述問題,關鍵是將每一個分支單獨寫成一個獨立的代碼段.采用顯示管理重載集的方法來解決C++的靜態分派問題.

2基于顯式管理重載集對C++靜態分派設計方式的改進

2.1顯示管理重載集的原理

在C++中,常常使用CRTP[13](奇特的遞歸模板模式)來識別管理重載集,但有時CRTP對于限制一般化的函數模板實參的范圍是很不夠的.比如我們可能希望通過函數模板操作內建類型(它們沒有基類)或者操作已有的第三方類型(不能修改它們).這時就可以使用Boost庫中的enable_if模板族[14]來非侵入地(nonintrusively)顯示管理模板函數的重載集,在編譯期決定一個參數類型的適當性.

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〉

{};

注意:當C的值為false時,enable_if_c〈C,T〉::type不存在,根據C++標準的重載決議規則,當一個函數模板的實參推導失敗時,它對被考慮準備調用的候選函數集沒有貢獻,并且不會導致一個錯誤.這個原則已經被David Vandevoorde和Nicolai Josuttis賦予“替換失敗并非錯誤(Substitution Failure Not An Error,SFINAE)”的名號[15].

2.2C++靜態分派設計方式的改進

重載集有時也能進行靜態分派,如果能在編譯期就能決定一個參數類型的適當性,則可用Boost的enable_if模板非侵入地(nonintrusively)顯式管理重載集,從而達到靜態分派的目的.用C++的顯示管理重載集方式來實現靜態分派問題.首先對模板函數

template 〈class T〉

void f(T x){

if(boost::is_class〈T〉::value){

std::cout〈〈x::value;//處理整型常量外覆器

}else{

std::cout〈〈x; //處理非整型常量外覆器

}

}

進行改進.重新設計后的代碼為:

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〉())將訪問不同的模板函數,只有匹配的那個模板函數才能實例化且不會出現編譯錯誤.

其次應用C++的顯示管理重載集方式來重新實現C++標準程序庫中的advance泛型算法.advance泛型算法[5]就是一個使用標簽分派的極好例子,advance用來將一個迭代器i移動n個位置,對不同類型的迭代器i實現的策略是不同的.如果i是隨機迭代器,則可用i+=n來實現.如果i是雙向迭代器,可以在運行期決定是否遞增或遞減迭代器;如果i是前向迭代器,則只能是向前步進多步.通過編譯期的標簽分派也可能達到靜態分派,它的基本思想是從運行期的泛型編程借用而來.一個標簽(tag)僅僅是一個空類[6](empty class),其唯一的用途就是在編譯期傳達信息.那么改進后的代碼為:

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是隨機迭代器

typename std::iterator_traits〈Iterator〉::iterator_category〉::type

〉::type

advance(Iterator& i,Distance n){

i+=n;

}

對于::advance(i,4)調用,同樣會根據迭代器i的類型來選擇一個合適的模板函數,達到迭代器i的遞增,其他可能使用一個給定的迭代器所未實現的重載則永遠不會被實例化.從以上實例過程來看:通過C++的顯示管理重載集方式來實現靜態分派問題就是將每一個可能的分支轉換成一個重載的模板函數.

2.3應用拓展

利用Boost庫中的enable_if模板族來非侵入地(nonintrusively)顯示管理模板函數的重載集實現C++的靜態分派問題時,同時也是根據模板函數的返回值來實現模板函數重載的過程.

下面的函數模板僅應用于算術類型的迭代器上并將容器中所有元素之和求出,通過元函數[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].

當有模板函數重載發揮作用時,這項技術就變得真正有趣起來.因為上述模板sum已經根據它返回值被限制為(接收)適當的參數,現在可以添加另一個重載,它允許我們計算出vector〈vector〈int〉〉的所有算術元素,以及其他算術類型的嵌套的容器.

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〉//結果無函數

〉::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”指出當條件被滿足時,該函數被從重載集中移走了,“lazy”則意味著函數的結果::type是以一個無參元函數調用第二個參數的結果[9].

注意,只有當迭代器的值類型是另一個迭代器時,inner_value〈Iterator〉才能被調用,這時sum(Iterator start,Iterator end)會調用第二個模板函數.當迭代器的值類型是算術類型時,sum(Iterator start,Iterator end)會調用第一個模板函數.對于其他類型的實參,當發生實參推導失敗時,那么在重載決議期間它對考慮準備調用的候選函數集沒有貢獻[10],這時并不會發生錯誤,這個過程當然也可以看成是根據模板函數的返回值來實現模板函數重載的過程.

3結束語

模板元編程是C++中一種高級編程技術,它處于編譯期,靜態分派技術是基于編譯期計算出來的結果來選擇不同的運行期行為或接口的程序設計方式,這種技術能將一些執行期的分派提前至編譯期,從而減少了編譯后的代碼長度,也提高了程序總體運行速度.這里則是通過使用Boost庫中的enable_if模板族來非侵入地(nonintrusively)顯示管理模板函數的重載集的方式來實現C++中靜態分派技術,提出了一種新的靜態分派思路,通過這種方式可以根據模板函數的返回值來重載模板函數.

參考文獻:

[1]DAVID A.C++模板元編程[M].榮耀,譯.北京:機械工業出版社,2010.

[2]ANDREI A.C++設計新思維[M].侯捷,於春景,譯.武漢:華中科技大學出版社,2003.

[3]DAVID V, NICOLAI M J. C++ template中文版[M].陳偉柱,譯.北京:人民郵電出版社,2004.

[4]HERBERT S.C++完全參考手冊[M].4版.北京:清華大學出版社,2007.

[5]王曉宇,錢紅兵.基于UML類圖和順序圖的C++代碼自動生成方法的研究[J].計算機應用與軟件, 2013,30(1):190195.

[6]周毅,顧進廣,張曉龍,等.一種面向復合屬性的自適應對象模型[J].計算機應用與軟件, 2008,25(11):137139.

[7]徐靜雯,周繼恩,施躍躍,等.軟件密集型系統的故障診斷技術研究[J].計算機應用與軟件, 2012,29(2):175178.

[8]黃山,陳昱松,王建偉,等.一種基于UML與SDL融合建模的組件系統測試方法[J].計算機應用與軟件, 2011,28(7):175177,182.

[9]唐峰,許第洪.SolidWorks與Pro/Engineer之間圖形數據交換方式的研究[J].湖南師范大學自然科學學報, 2011,34(1):3742.

[10]劉震,繆力.基于動態調用圖的Java程序修改影響分析技術[J].湖南師范大學自然科學學報, 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].閆鋒欣,譯.北京:電子工業出版社,2008.

[13]葉至軍.C++ STL開發技術導引[M].北京:人民郵電出版社, 2007.

[14]MATTHEW H A. 泛型編程與STL[M].侯捷,譯.北京:中國電力出版社,2003.

[15]ANDREW K, BARBARA M. C++沉思錄[M].黃曉春,譯.北京:人民郵電出版社,2008.

(編輯陳笑梅)

猜你喜歡
調用靜態模板
Inventors and Inventions
猜猜他是誰
基于HTML5靜態網頁設計
把握數學解題模板,輕松做題一二三
SOLIDWORKS Electrical清單模板定制方法
基于Android Broadcast的短信安全監聽系統的設計和實現
分布式系統負載均衡關鍵技術及其發展脈絡
文檔提效 用好WPS模板
利用RFC技術實現SAP系統接口通信
C++語言中函數參數傳遞方式剖析
91香蕉高清国产线观看免费-97夜夜澡人人爽人人喊a-99久久久无码国产精品9-国产亚洲日韩欧美综合