56 typedef TypeIist く char , Type1ist く signed char , unsigned char> > CharList ; ( 行末にある 2 つの > トークンの間には , 面倒ですが空白を入れなければなりません * 1 。 ) 第 3 章 タイプリスト タイプリストには値が存在しません : その本体は空であり , 状態を保持せず , 何の機能も定義されていない のです。実行時にも , タイプリストに値が保持されることはありません。唯一の存在意義は , 型情報を保持する ということだけなのです。このため , タイプリストにおける全ての処理は , 必然的に実行時ではなく , コンパイ ル時に行われます。タイプリストを実体化しても害はありませんが , そもそも実体化を意図して作られたもので はないのです。従って , 本書で「タイプリスト」と記述した場合 , タイプリストの値ではなく , タイプリストの 型を指すと思って下さい。タイプリストの値には , あまり面白味がなく , その型のみが使用されるのです。 ( セ クション 3.13.2 では , 値のコレクションを生成するためのタイプリストの使用法を解説しています。 ) こでは , 「テンプレート・パラメータには , 同じテンプレートの別な実体化を含む任意の型を指定できる」とい うテンプレートの性質を利用しています。これは , 古くから知られているテンプレートの性質であり , vector く vector く double> > といったにわか仕立ての行列を実装する場合にもしばしば利用されています。 Type1ist は 2 つのパラメータを受け入れるため , Type1ist の片方のパラメータを別の Type1ist で置き換えることによっ て , リストを無制限に拡張することができるわけです。 しかし , 些細な問題が 1 つあります。型を 2 つ以上保持したタイプリストは表現できるものの , 型をゼロ個 , あるいは 1 個しか保持していないタイプリストが表現できないのです。こで必要なのは , null リスト型であ り , 第 2 章で解説した Nu11Type クラスこそが , この目的に合致したものとなるのです。 全てのタイプリストは Nu11Type で終了しなければならないという規約を導入しましよう。 Nu11Type は , 伝 統的な C 言語によって提供されている文字列関数が終端子として使用している \ 0 と同じ意味を持っています。 これで , 要素が 1 つしかないタイプリストを定義することができるようになりました・ / / Nu11Type の定義は第 2 章を参照して下さい typedef Type1ist く int , NuIIType> OneTypeOnIy; char 系の要素を 3 っ保持したタイプリストは : typedef Type1ist く char , TypeIist く signed char , Type1ist く unsigned char , Nu11Type> > > AIICharTypes ; これで , 基本的なセルを合成することによっていくつでも型を保持できる , オープンな Type1ist テンプレート を作り出すことができたわけです。 それでは , タイプリストの操作方法を見ていくことにしましよう。 ( もう一度 , 念を押しておきますが , これ は Type1ist オプジェクトの操作ではなく Type1ist という型の操作を意味しています。 ) 面白い旅の始まりで す。それでは , 奇妙な , そして新しい規則に則った , 0 + の裏側に広がる世界一コンパイル時に実行されるプロ グラミングの世界に入っていきましよう。 、 1 訳注 : 空白を入れなければ , コンパイラはこれを > > 演算子であると解釈してしまいます。
2.7 コンパイル時における変換可能性と継承の検出 ( 話は逸れますが , MakeT や Test のように何も行わないだけでなく , 実際にはまったく存在しないような関数 を用いてこれだけのことができるなんて洒落てると思いませんか ? ) さて , この考えがうまく動作することも判ったので , 全てをクラス・テンプレートの中に詰め込んで , 型推論 における細かい内容を全て隠し , 結果だけを見えるようにしましよう : 39 int main() Conversion クラス・テンプレートは , sizeof (Test (MakeT() ) ) enum { exists public : static T MakeT() ; static Big Test( . static SmaII Test (U) ; class Big { char dummyC2] ; } ; typedef char Sma11 ; class Conversion template く class T, class U> sizeof(SmaII) } ; 以下のようにしてテストすることができます : 1-lSIng COUt くく くく くく namespace std; Conversion く char, char*> : : exists くく ' Conversion く double , int> : : exists くく ' Conversion く size_t, vector く int> > : :exists くく ' この小さなプログラムを実行すると , " 1 0 0 " と表示されます。 std : : vector が size-t を受け取るコンスト ラクタを実装していたとしても , そのコンストラクタは明示的なものであるため , 変換テストによって 0 が返さ れる点にご注意下さい。 この他にも conversion 中には , T と U が同じ型を表現している場合に true を返す , sameType という定数 を実装できます : template く class T, class U> CIass ConverS101i . 同上 . enum { sameType = false } ; conversion の部分的な特殊化を通じて sameType の実装を行います : template く class T> class Conversion く T, T> public :
26 の少ない型へキャストしてはいけないわけです : template く class TO, class From> TO safe—reinterpret—cast (From from) assert (sizeof (From) く = sizeof (TO) ) ; return reinterpret—cast く To>(from) ; p = safe—reinterpret—cast く char*>(i) ; この関数は , 従来の 0 + におけるキャストと同じシンタックスで使用することができます : char* int i 第 2 章テクニック テンプレート引数 To は明示的な記述から , テンプレート引数 From は i の型からコンパイラによって推論され ます。また , 各型のサイズ比較を条件として asse て t に記述しておくことで , 元の型における全てのピットが結 #define STATIC—CHECK(expr) { char unnamed [(expr) ? 1 列が不正なものと扱われるという点に基づいたものがあります (VanHorn 1997 ) : コンパイル時の表明に対する最も簡潔な解決策であり , 0 + と同様 C でも動作するものとして , 長さゼロの配 いうわけです。 す。この構造に対して , ゼロとなる式を引き渡せば , コンパイル時点でコンパイラがエラーを通知してくれると ものとして扱われ , ゼロとなる式を引き渡すと不正なものとして扱われるような言語構造を利用すれば良いので ル時にチェックを行うことができるはずです。つまりコンパイラに対して , 非ゼロとなる式を引き渡すと正当な しかし , 望みはあります。評価される式がコンパイル時の定数になるのであれば , 実行時ではなく , コンパイ ないのです。 また , あなたの顧客の前でプログラムがクラッシュするまでバグを眠らせておくようなことも避けなければなら フォームにアプリケーションを移植する時まで , 可搬性の無い部分全てを憶えておくことはできないはずです。 グラム中で滅多に実行されない分岐中に記述されているかもしれないからです。新しいコンパイラやプラット こういったエラーは , コンパイル時に検出される方が望ましいことは明らかです。これは , キャストがプロ トが得られるか , 表明工ラーのいずれかが発生するのです。 果の型に保持されることを保証するわけです。上記コードを用いた場合 , 実行時に , 正しい ( はずの、 1 ) キャス * 1 reinterpret-cast を用いる限り , ほとんどの処理系では正しいかどうかを確約することはできません。 char c = safe—reinterpret—cast く char>(somePointer) ; void* somePointer = return reinterpret—cast く TO> (from) ; STATIC-CHECK(sizeof (From) く = sizeof (TO) ) ; TO safe—reinterpret—cast (From from) template く class TO , class From> これを用いれば以下のように記述できます :
5.8 引数と戻り型の変換 cmd2 ( 4 , 4.5 ) ; 121 初期化と静的なキャストのいずれによっても , 本当に呼び出したいのは int と d 。 uble を受け取り void を返す TestFunction であるということが , コンパイラに伝わるわけです。 5.8 引数と戻り型の変換 理想的な世界では , Functor に対する変換が通常の関数呼び出しの変換と同じように機能して欲しいはずで す。つまり , 以下のような実行ができて欲しいわけです : #include く string> #include く iostream> #include "Functor . h' using namespace Std; / / この例で興味のない引数は無視する const char* TestFunction(doub1e , double) static const char buffer ロ " He110 , world! " ・ / / static なバッフアへのポインタを返すのは安全 return buffer ; int main() Functor く string, TYPELIST—2 (int , / / " wo て ld ! " と出力される cout くく cmd(10, 10 ) . substr(7) ; int)> cmd(TestFunction) ; 実際の TestFunction は , 少し違ったシグネチャ (double を 2 つ受け取り const char * を返す ) となって いるものの , Functor く string, TYPELIST-2(int, ⅱ nt ) > と結びつけることが可能です。こういったことが 期待できるのは , int が暗黙のうちに double へと変換され , const char * も暗黙のうちに string へと変換 されるためです。 0 + が受け付ける変換と同じものを F 皿 ct 。 r が受け付けないとした場合 , Functor は不必要 に柔軟性のないものとなってしまいます。 こういった要求を満足させるために , 新たなコードを追加する必要は無いのです。上記の例は , 現在 しかし , のコードでそのままコンパイルでき , 期待通りの実行結果が得られるのです。何故でしようか ? 答えは , また しても FunctorHand1er の定義方法にあります。 テンプレートの実体化が全て行われた後に , 例のコードがどのようになるのかを見てみましよう。以下の 関数 : . >::operator()(int i, int j) string Functor く . これは以下の仮想関数へと転送されます :
・ firstAvai1ab1eB10ck- 88 第 4 章小規模オブジェクトの割り当て このチャンク中における , 利用可能な最初のプロックを指すインデックス。 このチャンク中で利用可能なプロック数。 ・ b10cksAvai1ab1e- Chunk のインタフェースはとても単純です。工 nit は Chunk オプジェクトを初期化し , ReIease は割り当て られたメモリを解放します。 A11 。 cate 関数はプロックの割り当てを行い , Dea110cate はプロックの解放を 行います。 Chunk はサイズを管理していないため , A11 。 cate や Dea110cate ではサイズを引き渡さなければ なりません。これは , 上位層がプロック・サイズを管理するべきであるという考えからです。 Chunk に冗長な b10ckSize- メンバを保持した場合 , スペースと時間の無駄が発生することになるわけです。私たちは今 , 最下層 に居ることを忘れてはいけません一全てがこの層を利用することになるのです。さらに効率性を考慮し , Chunk にはコンストラクタ , デストラクタ , 代入演算子を定義しません。この層でコピーのセマンティックスを厳密に 定義してしまうと , Chunk をベクタとして格納する上位層の効率が低下してしまうからです。 この Chunk 構造から , 重要なトレード・オフを読みとることができます。 b10cksAvai1ab1e- および firstAvai1ab1eB10ck- は unsigned char 型であるため , 255 プロック以上のチャンクを保持することが できないのです ( キャラクタが 8 ビットのマシンの場合 ) 。読み進めていただければ判りますが , この決定は結 果的にそんなにまずいものではなく , 多くの問題を解決してくれるものとなります。 こからが面白い部分です。プロックは , 使用中であるか , 未使用であるかのいずれかです。つまり , 未使用 プロックには何らかの情報を格納することができるのです。これを利用して , 未使用プロックの最初のバイトに は , 次の未使用プロックのインデックスを格納します。 firstAvai1ab1eB10ck- に利用可能な最初のインデック スを保持すれば , 後は余計なメモリを必要とすることなく , 未使用プロックの単方向リンク・リストを実現でき るわけです。 初期化時点では , チャンクは図 4.4 のようになっています。 Chunk オプジェクトを初期化するコードは以下の ようなものです : void Chunk: : lnit (std: : size-t bIockSize , unsigned char blocks) new unsigned char [b10ckSize * blocks] ; pData— firstAvai1ab1eB10ck_ b10cksAvai1ab1e blocks ; unsigned char i unsigned char* p = pData for ( ; i ! = blocks; p + = bIockSize) 十十 i ; データ構造の中に単方向リンク・リストを溶け込ませることは , なかなかの名案です。これによりチャンク中の 利用可能なプロックを検索する際 , 余計なメモリを必要とせず , 高速かっ無駄のない方法を実現できるのです。 Chu Ⅱ k 中のプロックに対する割り当て , 解放は , 埋め込み型単方向リストの恩恵によって一定時間で完了するこ とができるわけです。 これでプロック数を unsigned cha てに収まる値に制限した理由が判ったでしよう。例えば unsigned short ( 多くのマシンでは 2 バイトとなります ) といった , より大きな値を使用した場合を考えてみて下さい。この場 合 , 些細な問題と大きな問題が出てくるのです :
テクニック 46 TL: : IndexOf く T, TYPEL 工 ST_4(signed char, short int , int , 10 Ⅱ g int)> : :value これは T が符号付き整数型である場合にのみ , ゼロ以上の数値を返します。 第 2 章 typedef TYPEL 工 ST—3 (float , double , 10 Ⅱ g double) F10ats ; typedef TYPEL 工 ST—3 ( b001 , char , wchar_t) OtherInts ; Signed 工 nts ; typedef TYPELIST_4(signed char, short int , int , long int) UnsignedInts ; unsigned int , unsigned long int) unsigned char , unsigned short int , typedef TYPELIST_4( public : . 上記の通り class TypeTraits template く typename T> TypeTraits 中における , プリミテイプな型に特化した部分の定義は , 以下のようになっています : enum { e num { enum { enum { enum { enum { isStdFundamenta1 isStdArith Ⅱ isStdF10at Ⅱ isStdArith = isStdIntegra1 Ⅱ isStdF10at } ; isStdFIoat = TL: : IndexOf く T, F10ats> : :value > = 0 } ; TL : : IndexOf く T, OtherInts> : : value > = 0 } ; isStd 工 ntegral isStdUnsigned 工 nt Ⅱ isStdSignedInt Ⅱ isStdSignedInt = TL : : IndexOf く T, Signed 工 nts> : :value > = 0 } ; TL : : IndexOf く T, Unsigned 工 nts> : :value > = 0 } ; isStdUnsigned 工 nt = Conversion く T, VOid> : : sameType } ; される間接参照のオーバーヘッドを避けるようにするわけです。 オーバーヘッド ( コンストラクタとデストラクタの呼び出し ) を避け , そしてスカラ型では参照によってもたら メンバへのポインタといった算術型から成り立っています。つまり , 複雑な型ではテンボラリに対する追加の の良い方法とは , 複雑な型を参照で , スカラ型は値で引き渡すことです。スカラ型は , 前述の en Ⅷ , ポインタ , を引数として関数間で授受する場合 , どのようにすれば最も効率が良くなるのでしようか ? 一般的に最も効率 テンプレートのコードでは , しばしば次のような質問に答える必要が出てきます : ある型 T のオプジェクト 2.10.3 パラメータ型の最適化 long long といった ) も扱えるようになっています。 基本型の判定を行うための実際の実装は , より洗練されたものとなっており , べンダ固有の拡張型 (int64 や して下さいね。 ここには戻ってくるように のか知りたい誘惑に耐えられないのであれば , 第 3 章を覗いてみて下さい。しかし , を迅速に判定することができるようになります。タイプリストと TL : : IndexOf の詳細がどのようになっている タイプリストと TL : : lndex0f の使用によって , 何度もテンプレートを特殊化することなく , 型に対する情報
5.11 要求のチェイニング 129 バインドは自動変換とうまく適合し , Funct 。てに信じられないほどの柔軟性をもたらします。以下はバインド と自動変換を組み合わせた例です : const char* Fun(int i , int j ) " くく j くく " ) called\n" cout くく Fun(" くく i くく " return " 0 " int main() Functor く const char* , TYPELIST—2(char, int)> fl (Fun) ; Functor く std: : string, TYPEL 工 ST—I (double)> f2( BindFirst(f1, 10 ) ) ; / / 出力 : Fun(10, 15 ) called f2 ( 15 ) ; 5.11 要求のチェイニング const Fun2& fun2) ; const Fun1& funl , Fun2 Chain ( template く class Fun1 , class Fun2> になっています : Loki は FunctorChain クラスとヘルバー関数 Chain を定義しています。そして , Chain の宣言は以下のよう とができるのです。 ) ンバ関数は , 以前の位置を保持した引数にバインドされているわけです。 ( ここでもバインドを手軽に用いるこ MacroCommand は , Document: :DeIeteChar および Window: :scroll から成り立っています。そして後者のメ のです。 Document:: 工 nsertChar メンバ関数は , アンドウ・スタックに MacroCommand をブッシュします。 独の Funct 。 r オプジェクトに複数の c 。 mm d を格納しておき , 単独の単位でそれらを実行する必要がある しく逆スクロールしてくれません。なんてはた迷惑なんでしよう。 ) このような逆スクロールを行うには , 単 ることによって , ウインドウも元の状態に「逆スクロール」して欲しいはずです。 ( ほとんどのエデイタは正 タではテキストの視認性を向上させるため , こういった動作を行うのです ) 。この場合 , 操作をアンドゥす て , テキスト・ウインドウの自動的なスクロールが行われる場合を挙げることができます ( ある種のエディ う。ある「実行」操作には , 複数の「アンドウ」操作を伴う可能性があります。例として , ・文字の挿入によっ この機能は , しばしば便利なものとなります。例えば , もう一度アンドゥ / リドウの問題を考察してみましょ マンドが順に実行されるわけです。 スという Command パターンの例を挙げています。 Macr 。 c 。皿 d を実行した場合 , それが保持している各コ GoF 本 (Gamma 他 1995 ) では , command をリスト状のコレクションとして保持した MacroCommand クラ
4.4 Chunk Sma Ⅱ 0bject Sma Ⅱ ObjA Ⅱ ocator F i xedA Ⅱ ocator Chunk 87 * オブジェクト・レベルのサービス * 透過性 . SmaIIObject からのみ クラスを導出可能 * さまざまなサイズの小さなオブジェクトを 割り当て可能 * パラメータ設定可能 * 固定されたサイズでのみ割り当て可能 * 特定のサイズでオブジェクトを割り当て可能 * 割り当てオブジェクト数の上限値が固定 されている 図 4.3 小規模オブジェクト・アロケータにおける階層構造 タの利点を与えることができるわけです。 void DeaIIocate(void* p, std: :size—t b10ckSize) ; void* AIIocate(std: :size—t bIockSize) ; void lnit (std: : size—t b10ckSize , unsigned char blocks) ; s t ruct Chunk / / そこからのみ操作されます。 / / これは FixedAIIocator 中で定義される構造であり , / / Chunk は平凡な古いデータ (POD) 構造です。 / / private なものは存在しません。 Chunk の定義は以下の通りです : す。チャンクの中に利用可能なプロックが存在しない場合 , 割り当て関数はゼロを返します。 Chunk には , メモリのチャンクからメモリ・プロックを割り当て , 解放するためのロジックが保持されていま 築時点では , プロック・サイズとプロックの数を指定します。 c u Ⅱ k 型の各オプジェクトは , 固定長のプロックを保持したメモリのチャンクを保持 , 管理するものです。構 4.4 Chunk できます。こうすることによって , とても簡単に使えるインタフェースとなるわけです。 用者側のコードは単に Sma110bject 基底クラスを導出するだけで効率の良い割り当ての利点を享受することが 危険であるため , FixedA110cator の private セクションに定義されています。 ) しかし , たいていの場合 , 利 また , Sma110bjA110cator と FixedA110cator を直接使うことも可能です。 (Chunk はプリミテイプすぎて 自身で管理を行うメモリに対するポインタに加えて , チャンクは以下の整数値を格納しています : b10cksAvai1ab1e_ firstAvai1ab1eB10ck_ unsigned char unsigned char* pData- void ReIease() ;
90 pData— f i rstAva ⅱ ableBlock blocksAva ⅱ ab 厄 . 254 第 4 章 2 253 プロック 254 255 小規模オブジェクトの割り当て 図 4.5 割り当てを 1 回行った後の Chunk オブジェクト。割り当てられたメモリは灰色で表現されています。 解放関数はまったく逆のことを行います。つまり , プロックを未使用プロックのリストに戻し , 0 ) ; 使用中である場合 , FixedA110cator は新たな Chunk を追加します。以下は FixedA110cator における関連部 当て要求が発生した場合 , FixedA110cator は要求に応えることができる Chunk を探索します。 Chunk が全て こういったことを実現するため , FixedA110cator は , Chunk オプジェクトをベクタとして管理します。割り は , 利用可能なメモリ容量に左右されるだけです。 ンクの数に制限されることなく , 固定サイズのプロックを割り当て , 解放する手段を管理しています。その能力 小規模オプジェクト・アロケータにおける , 次の階層は FixedA110cator です。 FixedA110cator は , チャ 4.5 FixedA110cator 最悪の場合に備えているのです。 を尊重したものとなっています。つまり , Chunk : : DeaIIocate に対して誤ったポインタが引き渡されるような 工ラー条件を網羅している訳ではありません ) 。メモリ割り当てにおいて , Chunk は偉大な C や C+ + のしきたり 解放関数は , さほど多くのことを行うものではありませんが , 数多くの表明を行っています ( これでも全ての + + b10cksAvai1ab1e- (toRe1ease ー pData—) / bIockSize) ; assert (firstAvai1ab1eB10ck— = = / / 切り捨てのチェック (toRe1ease ー pData—) / bIockSize) ; firstAvai1ab1eB10ck_ static—cast く unsigned char>( *toRe1ease = firstAvai1ab1eB10ck_ assert ( (toReIease ー pData-) % b10ckSize = = / / 整列のチェック unsigned char* t0Re1ease static—cast く unsigned char*>(p) ; assert (p > = pData-) ; void Chunk: :Dea110cate (void* p, std: : size—t b10ckSize) のパラメータとしてサイズを渡さなければならない点を忘れないようにして下さい・ bIocksAvai1ab1e ーを増加させるわけです。 Chunk はプロックのサイズを関知していないため , Dea110cate へ
4.4 Chunk pData— firstAva ⅱ ab 厄引 ock ー : 0 国 ocksAva ilab 厄 255 89 1 2 253 プロック 254 255 図 4.4 各プロックのサイズが 4 バイトのチャンク ( 255 プロック ) 整列の問題 (alignmentproblem) が発生します。プロックのサイズが 5 バイトのアロケータを構築したと ろうとしている「小規模」オプジェクト・アロケータの目的に合わなくなります。こちらが些細な問題です。 ・ sizeof (unsigned short) よりも小さなサイズのプロックを割り当てることができなくなり , 私たちが作 割り当て関数は , firstAvaiIab1eB10ck- によってインデックス付けされたプロックを取得し , 次の利用可能 ら , この制限によって違いは出てこないのです。 また , プロックがそれよりも大きな場合でも , あまりにも巨大なチャンクを割り当てることはないはずですか トといった非常に小さなものであったとしても , この制限によって問題が引き起こされることはないでしよう。 ( 大半のシステムでは 255 ) を超えるプロックを格納できないのです。プロックのサイズが 1 バイトから 4 バイ この設定によってチャンク中におけるプロックの最大数が制限されます。つまりチャンク中には , UCHARNAX ても , 整列問題は発生しないのです。 ラクタ型のサイズは 1 であるため , 基となるメモリを指すポインタが unsigned char を指している場合であっ 解決法は簡単です : 「舞台裏のインデックス」の型として unsigned char を用いるのです。定義によりキャ することによって , 定義されていない動作が引き起こされるのです。こちらが大きな問題です。 こういった 5 バイトのプロックを指すポインタを unsigned int へとキャスト 考えて下さい。この場合 , アウトを示しています。 はとてもいい感じで進んできています。図 4.5 では , 最初の割り当てを行った後の Chunk オプジェクトのレイ 0 ントというきわめて小さいもので済みます。そして最も大事なのは , 探索が存在しないという点です Chunk : : A110cate のコストは , 比較 1 回 , インデックス・アクセス 1 回 , 参照外し演算 2 回 , 代入 , return pResuIt ; —b10cksAvai1ab1e *pResuIt ; firstAvai1ab1eB10ck_ / / 次のプロックを指すよう firstAvai1ab1eB10ck- を更新する pData— + (firstAvai1abIeBIock- * bIockSize) ; unsigned char* pResu1t if ( !b10cksAvaiIabIe—) return 0 ; void* Chunk: :A110cate(std: :size—t b10ckSize) なプロックを参照するよう firstAvai1ab1eB10ck- を更新します。これは典型的なリスト処理です : こまで デクリメ