在C++中,術語“轉化”(conversion)描述的是從另外一個類型的值(value)獲取一個類型(type)的值的過程。可是有時候你會需要一種不同類型的轉化:可能是在你有一個類型時需要獲取一個值,或是其它的類似情形。在C++中做這樣的轉化是不尋常的,因爲類型域和值域之間隔有有一堵很嚴格的界線。可是,在一些特定的場合,你需要跨越這兩個邊界,本欄就是要討論該怎麼做到這個跨越。
映射整數爲類型
一個對許多的generic programming編程風格非常有幫助的暴簡單的模板:
template <int v>
struct Int2Type
{
enum { value = v };
};
對傳遞的每一個不同的常整型值,Int2Type“產生”一個不同的類型。這是因爲不同的模板的實體(instantiation)是不同的類型,所以Int2Type<0>不同於Int2Type<1>等其它的類型的。此外,產生類型的值被“存放”在枚舉(enum)的成員值裏面。
不管在任何時候,只要你需要快速“類型化”(typify)一個整型常數時,你都可以使用Int2Type。
問題描述:
當需要根據不同的值選擇不同的行爲時,普通的做法是利用if...else...或者case結構
來實現分支選擇。在一個型別模板化的函數過程中,則可能出現問題。
看下面的第一部分:
Part 1
如下的範型容器NiftyContainer的元素型別範型化爲T。在容器中需要實現元素的賦值行爲。非常規的自定義類型,其複製行爲是複雜的。 特別是具有多態特性的類型和普通的類型,其複製過程不一樣。例如,普通類型可以採用逐位複製的簡單方式,而具有多態特性(例如含有指針成員)的類對象則不宜採用 這種方式。此處假定普通類型的複製採用拷貝構造函數方式而多態的類型則採用一個專門的虛函數clone來做拷貝。
- #include <cstdlib>
- using namespace std;
- class Normal {
- int _i;
- public:
- Normal() { _i = 0; }
- Normal(const Normal& nml) { _i = nml._i; }
- // other members omitted
- };
- class Polym {
- int *_pi;
- Polym(const Polym&); // forbids copy-constructor
- void setp(int* i) { _pi = i; }
- public:
- Polym() { _pi = 0; }
- Polym* clone();
- // other members omitted
- };
- template<class T, bool isPolymorphic>
- class NiftyContainer
- {
- // other members omitted
- public:
- void DoSomething()
- {
- T* pSomeObj =new T;
- if (isPolymorphic) // branch1
- {
- T* pNewObj = pSomeObj->clone(); // customized behavior for copying
- // some operations on polymorphic object
- pNewObj = 0;
- }
- else // branch2
- {
- T* pNewObj = new T(*pSomeObj); // using copy-constructor
- // some operations on non-polymorphic object
- delete pNewObj;
- }
- delete pSomeObj;
- }
- // other members omitted
- };
- int main(int argc, char* argv[])
- {
- // using the container with elements' type Normal
- NiftyContainer<Normal, false> nc;
- nc.DoSomething(); // error: 類Normal沒有成員函數clone
- // using the container with elements' type Polym
- NiftyContainer<Polym, true> nc2; //error: 類Polym的copy-constructor private
- nc2.DoSomething();
- system("PAUSE");
- return EXIT_SUCCESS;
- }
錯誤解析
按照我們的預期,如果元素類型是多態的,那麼只執行branch1部分,否則只執行branch2,因爲兩種類型的元素有不同的拷貝函數。但是編譯器可不會理會這些,一律的都要加以編譯,因此出錯了。
對於模板來說,不同的參數意味着不同的類型。 此外,模板的編譯有一個特性,即沒有用到的模板函數是不會參與到編譯中的,只進行語法正確性的分析。 函數可以根據參數進行重載。
爲了做到“根據常數的值(此處就是isPolymorphic)只編譯其中某一個分支(例如branch1),而不編譯其它分支,從而保證最後的代碼中只包含需要的代碼”, 我們可以利用上述的模板編譯特性和函數重載特性。
- template<int v>
- struct Int2Type { // 創造了這個模板是爲了用不同的常數值產生不同的類型
- enum {value=v};
- };
- template<class T, bool isPolymorphic>
- class NiftyContainer
- {
- private:
- void DoSomething(T* pObj, Int2Type<true>) // function_p
- {
- T* pNewObj = pObj->Clone();
- // some operations on polymorphic object
- pNewObj = 0;
- }
- void DoSomething(T* pObj, Int2Type<false>) // function_n
- {
- T* pNewObj = new T(*pObj);
- // some operations on non-polymorphic object
- }
- public:
- void DoSomething(T* pObj)
- {
- DoSomething(pObj, Int2Type<isPolymorphic>());
- }
- // other members are omitted
- };
- int main(int argc, char* argv[])
- {
- // using the container with elements' type Normal
- NiftyContainer<Normal, false> nc;// ok
- // using the container with elements' type Polym
- NiftyContainer<Polym, true> nc2; // ok
- system("PAUSE");
- return EXIT_SUCCESS;
- }
在編譯過程中,根據傳遞的isPolymorphic模板參數值的不同,編譯器將選擇實現哪個函數。 若爲true,則實現function_p,而不會嘗試去編譯function_n。 這樣就成功做到了編譯期多態,而不是執行期多態。
這個技術的關鍵點就在於Int2Type模板。