テンプレヌトマゞックCallWithTypeパタヌン

芪愛なるハブロフチアン



この蚘事では、C ++でコンパむル時デヌタ 型 をランタむムデヌタ 敎数倀に、たたはその逆に 倉換する方法に぀いお説明したす。



䟋

int nType = ... ;



if  boost :: is_base_of < ISettable、 / * ... nTypeによっお隠された型をここで魔法のように解決したす... * / > :: value 

{

//䜕かをする

}

他に

{

//別のこずをする

}


このトピック党䜓は、「nTypeによっお隠された型をここで魔法のように解決する」のではなく、䜕を曞く必芁があるかを理解するこずを目的ずしおいたす。



結果にのみ興味がある堎合は、最埌のセクションたでスキップしおください。



ちょっずした歎史



それはすべお、圓番では、ほが次のように動䜜するオブゞェクトの耇雑な工堎で䜜業する必芁があるずいう事実から始たりたしたオブゞェクトを䜜成するには、動的デヌタの深byに基づいお、䜜成されるオブゞェクトのタむプのIDを返す特定の関数が呌び出されたした このIDはスむッチケヌスに分類され、実際に次のような必芁なオブゞェクトが䜜成されたす。

int nObjectType = ResolveObjectType  ...  ;



boost :: shared_ptr < IObject > pObject = CreateObject  nObjectType  ;


特定のランタむム条件䞋で、フォヌムのラッパヌをいく぀かのオブゞェクトに掛ける必芁があるこずが刀明するたで、このシステムではすべおがうたくいきたした。

テンプレヌト < クラス TObject >

クラス CFilter  パブリック TObject

{

仮想 ブヌル FilterValue  ...  { ... } ;

} ;


このようなラッパヌは、特定の条件䞋で必芁な特定の新しい機胜をオブゞェクトに远加したした。 圓然、最初は垞にすべおのタむプのオブゞェクトに比べお、ラッパヌは垞に少しだけハングし、少しだけハングしおいたしたこのため、ほんの数行のコヌドを远加する必芁がありたしたが、非垞に簡単でした。 ただし、䞍必芁なラッパヌは、䜜業のロゞックに害を䞎えるこずはありたせんが、オブゞェクトのサむズを倧きくしたすが、これは受け入れられたせんでした。ファクトリヌ自䜓ずその助けを借りお䜜成されたオブゞェクトは、パフォヌマンスずメモリ消費の点で重芁でした。



したがっお、ラッパヌをオプションで含める必芁がありたした。 すぐに問題が発生したした

このすべおを実珟し、私は座っお考えたした。 さらに、GetObjectType...によっお返される倀の背埌に隠されおいる型を描画する方法を孊習するだけで、コンパむラヌにずっお明確になるようになりたした。぀たり、 粟神で物事を曞くこずができるように

int nType = ... ;



if  boost :: is_base_of < ISettable、 / * ... nTypeによっお隠された型をここで魔法のように解決したす... * / > :: value 

{

//䜕かをする

}

他に

{

//別のこずをする

}


私の最初の考え「これは䞍可胜です」



テンプレヌトの魔法、たたは䞍可胜は可胜です



もう少し考えた埌、次の2぀の関数を蚘述するだけでよいずいう結論に達したした。

// TObjectに察応するオブゞェクトタむプ蚘述子を返したす。

テンプレヌト < クラス TObject >

むンラむン int MakeDescriptor   ;



// nullptrを指定しおrcFunctorを呌び出し、nTypeDescriptorによっお隠された実際のオブゞェクトタむプを呌び出したす。

テンプレヌト < クラス TFunctor >

むンラむン タむプ名Impl :: ResolveReturnType < TFunctor > :: タむプ CallWithType  const TFunctor  rcFunctor、 int nTypeDescriptor  ;


ここではすべおが明確です

この方法で䜿甚できたすこの䟋では、むンタヌフェむスからの蚘述子によっお゚ンコヌドされた型が継承されるかどうかを刀断したす。

テンプレヌト < クラス TKind >

struct IsKindOfHelper

{

typedef bool

R ;



むンラむン ブヌル挔算子   ...  const

{

falseを 返し たす 。

}



むンラむン ブヌル挔算子   TKind *  const

{

trueを 返し たす 。

}

} ;



テンプレヌト < クラス TObject >

むンラむン bool IsKindOf  int nTypeDescriptor 

{

Return CallWithType  IsKindOfHelper < TObject >   、nTypeDescriptor  ;

}



...



int nType = ... ;



if  IsKindOf < ISettable >  nType  

{

//䜕かをする

}

他に

{

//別のこずをする

}


タむプリストず蚘述子。


それでは、実装に取り​​かかりたしょう。 たず、タむプずタむプの蚘述子に䞀臎するコンパむルタむムテヌブルが必芁です。 ここで最も簡単なオプションはLoki :: Typelistです。これは次の圢匏の構造です。

テンプレヌト < クラス T、 クラス U >

struct typelist

{

typedef Tヘッド;

typedef U Tail ;

} ;


か぀おこの構造を熟考したこずで、C ++に぀いおの考えが完党に逆さたになりたした。 なぜ必芁なのか芋おみたしょう。 すべおが非垞に簡単です任意の長さのタむプのヘルプリストが蚭定されおいたす

typedef Loki :: Typelist < int 、Loki :: Typelist < char 、Loki :: Typelist < void 、Loki :: NullType >>>

TMyList ;


ここでは、3぀の芁玠の型のリストが指定されおいたすint、char、void。 Loki :: NullTypeはリストの終わりを意味したす。 このリストから特別なメタ関数を䜿甚しお、タむプむンデックスずむンデックスによるタむプを抜出できたす。

// int MyInt;

Loki :: TypeAt < TMyList、 0 > :: Result MyInt ;



// char MyChar;

Loki :: TypeAt < TMyList、 1 > :: Result MyChar ;



int nIndexOfChar = Loki :: IndexOf < TMyList、 char > :: value ;


これらのメタ関数はすべお、コンパむル段階で「呌び出され」、実行時間のオヌバヌヘッドを必芁ずしたせん。 Lokiの詳现に぀いおは、 Wikipediaを参照しおください。ラむブラリ゜ヌスぞのリンクもありたす。 「Modern Design in C ++」Alexandrescuの本では、すべおがどのように機胜するかを知るこずができたす。



実際には、 Boost MPLラむブラリヌを䜿甚したした。 それはより耇雑ですが、その可胜性ははるかに広いです。 実隓により、コンパむラは玄2000皮類のオブゞェクトに耐えるこずが瀺され、その埌、次の図が芳察されたす。



コンパルルしたす..



型リストを介したパタヌンの実装。


アむデア
 特定のタむプリスト内のすべおの既知のオブゞェクトタむプをリストしたす。 次に、特定の型のむンデックスは型蚘述子になりたすが、型蚘述子を知っおいる堎合は、リストでそれを芋るず型自䜓を衚瀺できたす。 唯䞀の問題は次のずおりです。むンデックスによる型掚論の堎合、むンデックスは定数コンパむル時の倀ずしお衚珟する必芁がありたす。 数倀倉数の倀を、mpl :: int_ <value>ずいう圢匏の察応する型に倉換する方法を孊習する必芁がありたす。 


名前空間のブヌストを䜿甚し たす 。



名前空間 Impl

{

// 既知のオブゞェクトタむプのリスト。

/ **珟実の䞖界では、この構造はテンプレヌトによっお構築されおいたす。 * /

typedef mpl :: リスト < TObjectType1、TObjectType2、TObjectType3 >

TKnownObjectTypes ;



// 既知のオブゞェクトタむプの数。

typedef mpl :: サむズ < TKnownAtomTypes > :: タむプ

TKnownObjectTypesCount ;

}



名前空間 Impl

{

// このメタ関数は、TKnownObjectsからTObjectのむンデックスを返したす。

/ ** TObjectがTKnownObjectsに存圚しない堎合、-1を返したす* /

テンプレヌト < クラス TObject >

struct MakeDescriptorImpl

 / * if * / mpl :: eval_if <

/ *TObjectを芋぀ける== end * /

is_same <

typename mpl :: find < TKnownObjectTypes、TObject > :: type 、

mpl :: end < TKnownObjectTypes > :: タむプ > 、

/ * -1を返したす* /

mpl :: identity < mpl :: int_ < -1 >> 、

/ *その他の距離を返す開始、怜玢TObject* /

mpl :: 適甚 <

mpl :: 距離 <

mpl :: begin < TKnownObjectTypes > :: type 、

mpl :: find < TKnownObjectTypes、_ >> 、

TObject >> :: タむプ

{

} ;



// TObjectTypeでTFunctorを呌び出すのに圹立ちたす*

テンプレヌト < クラス TFunctor >

struct CallWithObjectTypeHelperPointerBased

{



公開 



// typename ResolveReturnType <TFunctor> :: TFunctorに評䟡される型:: R if

// TFunctor :: R typedefが存圚したす。それ以倖の堎合は、voidに評䟡されたす。

typedef typename ResolveReturnType < TFunctor > :: タむプ

R ;



保護された 



const TFunctor 

m_rcFunctor ;



公開 



CallWithObjectTypeHelperPointerBased  const TFunctor  rcFunctor 

 m_rcFunctor  rcFunctor 

{

}



// この関数は、CallWithInt...によっお呌び出されたす。

テンプレヌト < クラス TIndex >

R挔算子   TIndex  const

{

//むンデックスでオブゞェクトタむプを怜玢

typedef typename mpl :: < TKnownObjectTypes、TIndex > :: タむプ

TObject ;



//実際のオブゞェクト型ぞのポむンタでファンクタヌを呌び出したす

return m_rcFunctor   TObject *  NULL  ;

}



// この関数は、CallWithInt...によっお呌び出されたす。

R挔算子   mpl :: void_  const

{

//蚘述子が壊れおいたす、特別な倀でファンクタヌを呌び出したす

return m_rcFunctor  mpl :: void_    ;

}

} ;



}



// TObjectに察応するオブゞェクトタむプ蚘述子を返したす。

テンプレヌト < クラス TObject >

むンラむン int MakeDescriptor  

{

//䞍明なオブゞェクトタむプのオブゞェクトタむプタむプの蚘述を詊みたす

BOOST_STATIC_ASSERT  Impl :: MakeDescriptorImpl < TObject > :: value  = - 1  ;



//蚘述子を返したす。これは実際にはコンパむル時に生成される定数です。

return Impl :: MakeDescriptorImpl < TObject > :: value ;

}



// nObjectTypeDescriptorに察応するTObject *でrcFunctorを呌び出したす。

テンプレヌト < クラス TFunctor >

むンラむン タむプ名Impl :: ResolveReturnType < TFunctor > :: タむプ CallWithType  const TFunctor  rcFunctor、 int nObjectTypeDescriptor 

{

// intで呌び出したす

// Impl :: CallWithObjectTypeHelperPointerBased <TFunctor>rcFunctor

// mplを持぀ファンクタヌ:: int_ <N>匕数、Nはコンパむル時定数

// nObjectTypeDescriptorの倀に察応。

// nObjectTypeDescriptor <0の堎合|| nObjectTypeDescriptor> = TKnownObjectTypesCount

// mpl :: void_でファンクタヌが呌び出され、タむプ蚘述子が壊れおいるこずを瀺したす。

return Impl :: CallWithInt < mpl :: int_ < 0 > 、TKnownObjectTypesCount > 

Impl :: CallWithObjectTypeHelperPointerBased < TFunctor >  rcFunctor  、

nObjectTypeDescriptor  ;

}


コヌド党䜓に぀いお詳现にコメントしようずしたしたが、ただかなり耇雑なので、コメントがいく぀かありたす。



1。

typename ResolveReturnType :: typeは、コンパむラによっおTFunctor :: Rずしお解釈されたす。 TFunctor :: Rが無効な匏である堎合たずえば、R型の定矩がTFunctorにない堎合、typename ResolveReturnType :: typeはvoidず解釈されたす。 はい、可胜です。 いいえ、私は嘘を぀いおいたせん 。 実装は、CallWithIntの実装を説明するリンクの䞋に衚瀺できたす。



2。

MakeDescriptorImplはboost :: mplで積極的に動䜜し、嚁圧的に芋えたす。 わかりやすくするために、コメントにはstlに同様の匏が含たれおいたすもちろん、コンパむル段階では適甚されたせん。 むンデントスタむルはSchemeから匕き裂かれたす 。 関数型プログラミング蚀語に粟通しおいる人は、C ++でのメタプログラミングテンプレヌトマゞックが関数型蚀語であるこずを理解する必芁がありたす。



3。

CallWithIntは、有効な倀の特定の領域から実行時敎数倀をコンパむル時敎数倀に倉換したす。 この関数は少し埌で実装したす。

 䟋42はmpl :: int_ <42>に倉換されたす 


4。

mpl :: listを䜿甚する代わりにバむナリツリヌを䜿甚するず、実装はコンパむル速床の点でより効率的になりたす。 残念ながら、そのような構造は芋぀かりたせんでしたが、私自身は長い間曞きたした。 私たちのプロゞェクトでは、これは重芁ではなく、500未満のタむプの数がそのように機胜したす。



5。

実行時のパフォヌマンスの芳点から、このコヌドは非垞に高速です。 CallWithIntは、以䞋で説明するように、入れ子になったスむッチケヌスで機胜するため、数倀を型に倉換するには、オフセット付きの無条件ゞャンプを数回行うだけで枈みたす。 逆倉換の堎合、䜕も必芁ありたせん。 MakeDescriptorは定数にむンラむン化したす。



6。

実䞖界では、既知のオブゞェクトのリストは、ほが次のようにテンプレヌトマゞックを䜿甚しお䜜成されたす。

このアクションは、コンパむル段階でテンプレヌトを䜿甚しお実行され、玄200行のコヌドが必芁です。 新しいラッパヌおよびラッパヌがもたらすすべおの新しいタむプを远加するには、玄10行だけを蚘述する必芁がありたすが、新しいタむプは既存のコヌドによっお自動的に取埗されたす。



7。

䞊蚘の関数に加えお、実際には、さらにいく぀かのバリアントが実装されおいたすたずえば、MakeDescriptorNonStrictは、科孊に未知の型に適甚された堎合に-1通垞のコンパむル゚ラヌではないを返したす。 CallWithTypeには、たずえば2぀のポむンタヌを䜿甚しおファンクタヌを呌び出す他のオプションもありたす1぀は継承ツリヌに特化するために䜿甚し、もう1぀は実際の型を決定するために䜿甚できたす。



CallWithIntリリヌス


 最埌の最も難しいステップを実行するこずは残りたす。型mpl :: int_ <N>のファンクタヌを呌び出す関数を䜜成したす。ここで、Nはランタむム倉数内に栌玍された倀に察応したす。 これはおそらく最も難しい郚分です。なぜなら、 ランタむム倀を型に倉換するのは圌女です。 


アむデアは非垞に簡単です。

 スむッチ、たずえば100個の芁玠を持぀関数を䜜成したす。この関数は倀mpl :: int <N>を持぀ファンクタヌを呌び出す必芁がありたす。ここで、Nは関数に枡される倉数の倀に察応したす。 別の間隔を倉換するように求められた堎合、単玔に远加の䞍正行為を行いたす。オフセットずビンぞの分割です。 したがっお、たずえば、56 ... 156の間隔で数倀を型に倉換するように求められた堎合、56に枡された倉数から毎回枛算するだけでよく、型に倉換した埌、56を远加したすただし、型に。 間隔200..400から数倀を倉換するように求められた堎合、最初にそれをセクション「100」に分割し、次にセクションの数ずセクション内のオフセットを蚈算する必芁がありたす。 


私はそれを䞍可解に説明しおいるず思うので、 ここにたくさんのキラヌコヌドがありたす 。



備考



0。

倚くのブナ:(



1。

実際には、スむッチには100のケヌスがありたす倀は実隓的に遞択され、コヌドは2以䞊の任意の倀に察しお動䜜可胜です。



2。

珟実の䞖界では、スむッチケヌスはマクロによっお生成されたす。



3。

速く動䜜したす。 ずおも速い。 このような残忍な衚珟でこれを支払う必芁がありたす。 コンパむラは、末尟再垰を切り替えお単玔な実装を最適化できたせんでした:(



申蟌み



これで次のこずができたす。



1.オブゞェクトを䜜成するには、オブゞェクトのタむプを既知のタむプのリストに远加したす。 以前は、新しいタむプのオブゞェクトを远加する堎合、すべおのスむッチケヌスをタむプごずに怜玢する必芁がありたしたが、スむッチケヌスはたったくありたせん。぀たり、すべおの新しいタむプのオブゞェクトが「すぐに」サポヌトされたす。 この䟋は、スむッチケヌスの消倱を瀺しおいたす。



それは

int nObjectType = ResolveObjectType  ...  ;



スむッチ  nObjectType 

{

ケヌス 1 

新しい TObject1  を 返し たす。

ケヌス 2 

新しい TObject2  を 返し たす。

ケヌス 3 

新しい TObject3  を 返し たす。

ケヌス 4 

新しい TObject4  を 返し たす。

/ * ... 100件以䞊のケヌスがここに入りたす* /

デフォルト 

NULLを 返し たす 。

} ;


次のようになりたした

// このラッパヌにより、実際のオブゞェクトタむプを刀別できたす。

テンプレヌト < クラス TBase、 クラス TObject = TBase >

struct CTypeWrapper

 パブリック TBase

{

// このむンスタンスに察応する型蚘述子を返したす。

仮想 æ•Žæ•° TypeDescriptor   const

{

MakeDescriptor < TObject >  を 返し たす。

}

} ;



// オブゞェクトを䜜成するのに圹立ちたす。 実際、これはオブゞェクト型で呌び出されるファンクタヌです。

クラス CreateObjectHelper

{



公開 



//戻り型

typedef IObject *

R ;



プラむベヌト 



テンプレヌト < クラス TBase、 クラス TObject >

むンラむン TBase * MakeObject   const

{

新しい CObjectTypeWrapper < TBase、TObject >  を 返し たす。

}



公開 



// 䞀般的なケヌス

テンプレヌト < クラス TObject >

むンラむン TObject *挔算子   TObject * 、...  const

{

MakeObject < TObject、TObject >  を 返し たす。

}



むンラむン IObject *挔算子   boost :: mpl :: void_  const

{

assert   "Type Descriptor Is BrokenMust'n not here here  ;



NULLを 返し たす 。

}



公開 



// IObjectType1から掟生したオブゞェクトの特殊なケヌス

テンプレヌト < クラス TObject >

IObject *挔算子   TObject * 、IObjectType1 *  const

{

// ...

}



// IObjectType2から掟生したオブゞェクトの特殊なケヌス

テンプレヌト < クラス TObject >

IObject *挔算子   TObject * 、IObjectType2 *  const

{

// ...

}

} ;



...



int nObjectType = ResolveObjectType  ...  ;



ObjectTraitsを返す:: CallDoublePointerBasedFunctorWithObjectType 

CreateObjectHelper   、

nObjectType  ;



2. dynamic_castを䜿甚せずに、オブゞェクトの実際のタむプに応じお、継承ツリヌの䞭倮にあるクラスの動䜜を倉曎したすCallWithTypeを䜿甚したアプロヌチは、クラス階局で玄50倍高速です。

テンプレヌト < クラス TKind >

struct IsKindOfHelper

{

typedef bool

R ;



むンラむン ブヌル挔算子   ...  const

{

falseを 返し たす 。

}



むンラむン ブヌル挔算子   TKind *  const

{

trueを 返し たす 。

}

} ;



テンプレヌト < クラス TObject >

むンラむン bool IsKindOf  int nTypeDescriptor 

{

Return CallWithType  IsKindOfHelper < TObject >   、nTypeDescriptor  ;

}



...



// TypeDescriptorは、仮想オブゞェクトであり、実際のオブゞェクトタむプの蚘述子を返したす。

//前の䟋で瀺した実装この関数を手動で蚘述する必芁はありたせん

if  IsKindOf < ISettable >  this- > TypeDescriptor    

{

//䜕かをする

}

他に

{

//別のこずをする

}


3.型ずしお盎接型蚘述子を含む倉数を操䜜する機胜。 これは、どの皮類のオブゞェクトタむプを䜜成するかを決定する手順で非垞に圹立ちたすタむプが倚くの倖郚芁因に䟝存する堎合。

int ApplySomeWrapper  int nType 

{

bool bShouldBeWrapperApplied = ... ;



if  bShouldBeWrapperApplied 

{

// IsWrapperApplicableは、CallWithTypeを䜿甚しおnTypeを実際の型TObjectに倉換したす

// WrapperTraitsを呌び出したす:: CSomeWrapper :: IsApplicable <TObject> ::倀メタ関数

if  IsWrapperApplicable < WrapperTraits :: CSomeWrapper >  nType  

{

// IsWrapperApplicableは、CallWithTypeを䜿甚しおnTypeを実際の型TObjectに倉換したす。

// WrapperTraitsを呌び出したす:: CSomeWrapper :: MakeWrappedType <TObject> ::型メタ関数

//ラップされた型を解決するために、この型のMakeDescriptorを呌び出したす。

MakeWrappedType < WrapperTraits :: CSomeWrapper >  nType  ;を返したす。

}

}



nTypeを返したす。

}



All Articles