102 第 4 章小規模オブジェクトの割り当て class ThreadingM0de1 = DEFAULT-THREADING , std : : size_t chunkSize = DEFAULT_CHUNK_SIZE, std : : size—t maxSma110bjectSize = MAX—SMALL-OBJECT-SIZE class Sma110bject public : static void* operator new(std: : size—t size) ; static void operator delete(void* p, std: :size—t size) ; virtual NSma110bject ( ) { } ・ Sma110bject の実体化から導出を行うことによって , 小規模オプジェクト・アロケータの利点を享受するこ とができます。 Sma110bject クラス・テンプレートの実体化はデフォルト・パラメータ (Sma110bject く >) を使って , あるいはスレッド・モデルやメモリ割り当てパラメータを使って微調整を行うことができます。 ・マルチスレッド環境下で ne を用いてオプジェクトを生成する場合 , ThreadingMode1n0 ラメータにはマ ルチスレッド・モデルを指定しなければなりません。新 readingM 。 del に関する情報については , 付録を参 照して下さい。 ・ DEFAULT-CHUNK-SIZE のデフォルト値は 4096 です。 ・ MAX-SMALL-OBJECT-S 工 ZE のデフォルト値は 64 です。 ・ #define によって DEFAULT-CHUNK-SIZE や MAX-SMALL-OBJECT-SIZE のいずれか , または双方のデフォル ト値をオーバーライドすることができます。展開後 , マクロは std : : size-t 型 ( または等価な型 ) の定数 値へと展開されなければなりません。 ・ #define によって DEFAULT-CHUNK-SIZE や MAX-SMALL-OBJECT-S 工 ZE のいずれかをゼロにした場合 , Sma110bjA11 。 c. h は条件コンパイルを用いて , 直接自由領域のアロケータに要求を転送するコードを 生成します。インタフェースは , 同じまま残ります。この機能は , 特殊化されたメモリ割り当てを使用する 場合と使用しない場合で , プログラム動作の比較を行う際に有効です。
2.1 コンパイル時の表明 27 使用しているシステムのポインタが char よりも大きい場合 , 長さゼロの配列生成を試みようとするため , コン パイラによってエラーが通知されるはずです。 このアプローチが抱える問題は , 通知されるメッセージがまったくと言っていいほど意味の無いものである点 です。「長さゼロの配列を生成することはできません」といったメッセージから「 char 型はポインタを保持する には小さすぎます」という意味を汲み取ることはできないでしよう。可搬性のある形でエラー・メッセージを提 供するのは非常に難しいことです。工ラー・メッセージについては遵守すべき規則というものが無く , 完全にコ ンパイラまかせになっているのです。例えば , 工ラーが未定義変数を参照している場合であっても , その変数の (Compi1eTimeError く (expr) ! = 0 > ( ) ) #define STATIC—CHECK(expr) \ template く > struct Compi1eTimeError く true> { } ; template く b001> struct Compi1eTimeError ; せん : で運が良ければ , コンパイラはエラー・メッセージ中に該当テンプレートの名前を表示してくれるかもしれま 少しましな解決策として , 参考になるような名前を付けたテンプレートを用いる方法が考えられます。これ 名前がエラー・メッセージ中に表示されるとは限らないのです。 も真となることは保証されていません。 ) では , 以下のコードを記述した場合に何が起こるのかを考えてみま こでは , sizeof(char) く sizeof(void*) が成立すると仮定しています。 ( 規格によれば , これが必ずし (void) sizeof (Compi1eTimeChecker く (expr) ! = 0 > ( (ERROR-##msg() ) ) ) ; \ class ERROR-##msg { } ; \ #define STATIC—CHECK(expr, msg) \ template く > struct Compi1eTimeChecker く false> { } ; CompiIeTimeChecker( . template く b001> struct Compi1eTimeChecker より判りやすいでしよう : 合 , Compi1eTimeError という名前は , もはや判りやすいものではありません。 compi1eTimeChecker の方が , という制限が残ります。この改良版 c 。 mpi1eTimeErr 。 r は以下のようなコードとなります。このようにした場 ・メッセージは C+ + の正当な識別子 ( 空白は使用できない , 数字で開始できない等 ) でなければならない , ラメータを表示させることができないか考えてみましよう。この手法では , 引き渡しを行うカスタマイズ版工 ないでしようか ? STATIC-CHECK に追加のパラメータを引き渡し , 何とかしてエラー・メッセージ中にそのパ もちろん , まだまだ改善する余地は残されています。こういったエラー・メッセージのカスタマイズはでき ものであり , コンパイラやプログラムのバグでは無いということが判断できます。 いったメッセージがコンパイラによって表示されるわけです。このメッセージによって , 該当工ラーが意図的な Compi1eTimeError く false> の実体化を試みた場合 , 「 Compi1eTimeError く false> は未定義の特殊化です」と Compi1eTimeError は , プーリアン定数の true という値に対してしか定義されていません。このため , Compi1eTimeError は , 型以外のパラメータ ( プーリアン定数 ) を受け取るテンプレートです。しかし
112 第 5 章汎用のファンクタ さらに , 一般化した世界では , パラメータの数は一定しておらず , それぞれはどのような型にもなり得るので す。引き渡される可能性のある型や数値を制約する根拠など , どこにもありません。 各 Functor は , その戻り型と引数の型によって定義されるということが , ここで得られる結論です。こういっ た要求に対応するには , 関数呼び出しパラメータが可変となる , 可変テンプレート・パラメータというちょっと 恐ろしい言語サポートが必要になるわけです。 残念なことに こういった言語サポートは殆どありません。可変テンプレート・パラメータはまったくサポー トされていません。可変引数関数は C+ + にも存在しますが ( C にあるものと同じです ) , C の場合は細心の注意 を払えばそれなりの作業をこなすことができるものの , C+ + の場合にはうまくいかないのです。可変引数は , 嫌 われ者の省略子 (printf や sc f 等で使われています ) によってサポートされています。 printf や scanf の 呼び出し時に , フォーマット記述に適合した引数の型を適切な数だけ与えない場合 ( これは良く見かける危険な 間違いです ) , 省略子を用いた関数の欠点が浮き彫りになってきます。可変パラメータのメカニズムは , 安全で はなく , 低水準であり , C+ + のオプジェクト・モデルには合わないものなのです。 言で言えば , 省略子を一度 でも使えば , 型の安全性 , オプジェクトのセマンティックス , 参照型に対するサポートが存在しない世界に放り 出されることになるわけです ( 本格的なオプジェクトと省略子を併用すると , 定義されていない動作が引き起こ されやすくなります ) 。呼び出された関数側は , 引数の数すら判らないのです。実際 , 省略子が使われている場 所は , 0 + にはあまり残っていません。 代替策として , Funct 。てが受け付ける引数の数の上限値を ( 十分大きな数に ) 固定してしまう手が考えられま す。固定の数値として何を選択するかは , プログラマが行わなければならない作業の内で最もつまらないものの ーっでしよう。しかし , 経験に基づいて数値を決定できるはずです。ライプラリ ( 特に昔のもの ) 中の関数は , ほとんどのものが 12 ケまでのパラメータで実現されています。このため , 引数の数の上限を 15 ケにします。 の任意の決定は封印し , 二度と考えないようにしましよう。 こういった決定をしても人生が楽になるわけはありません。 0 + では , 同じ名前となるテンプレート しかし , が , 異なった数のパラメータを保持することを許していません。つまり , 以下は不正なコードとなります : / / 引数を受け取らない Fu Ⅱ ct 。 r template く typename Resu1tType> class Functor / / 引数を 1 つ受け取る Functor template く typename Resu1tType , class Functor typename Parm1> テンプレート・クラスの名前を , Functor1, Functor2 のようにするのも厄介なことです。 こで , 第 3 章で定義した , 型の集合を扱うための汎用機能 , つまりタイプリストを思い出してください。 Functor のパラメータは , 型のコレクションであるため , このタイプリストがうまく適合するのです。タイプリ ストを用いた Funct 。ての定義は , 以下のようになります :
5.4 Functor クラス・テンプレートのスケルトン / / 任意の数および型の引数を受け取る Functor template く typename ResuItType , class TList> class Functor 実体化は以下のような形で可能になります : / / int と double を受け取り , double を返す Functor の定義 Functor く double , TYPEL 工 ST—2 (int , double) > myFunctor; 113 このアプローチを使えば , タイプリスト用として定義してきた全ての機能を再利用でき , Fu Ⅱ ct 。て用として類似 のものを開発しなくても良いというメリットも出てきます。 これから見ていただくように , タイプリストは有効であるものの , 任意の数の引数を受け取るためには , Funct 。 r の実装でまだまだ面倒な繰り返しが必要です。当面は , 引数の最大数を 2 と考えておくことにしましょ う。 Functor. 巨ファイルでインクルードされるものは , 上述したように最大 15 になっています。 Functor によってラップされるポリモフィズムに則ったクラス FunctorImp1 には , Functor と同じテンプ レート・パラメータが保持されます * 4 ・ template く typename R, class TList> class FunctorImp1 ; FunctorImp1 には , 関数呼び出しを抽象化する , ポリモフィズムに則ったインタフェースを定義します。 Funct 。 rlmpl の明示的な特殊化は , パラメータの数毎に定義します ( 第 2 章を参照して下さい ) 。そして各特殊 化によって , 適切な数と型のパラメータに対する純粋な仮想の。 perat 。 r ( ) を以下のようにして定義します : virtual R operator() (PI) public : class FunctorImp1 く R, TYPELIST—I (PI)> template く typename R, typename PI> virtual NFunctorImp1 ( ) { } virtual FunctorImpI* CIone ( ) const virtual R operator() ( ) public : class FunctorImp1 く R, NuIIType> template く typename R> プレート・パラメータに対しては class を用いています。 従って , ( int のような ) プリミテイプ型となるテンプレート・バラメータに対しては typen e を使用し , ユーザ定義型となるテン “テンプレート・パラメータの記述で typen e や class を使用することによって違いが発生することはありません。本書では規約に virtual TunctorImpI ( ) { } virtual FunctorImpI* CIone ( ) const =
5.6 ファンクタの取り扱い もファンクタなのです ) 。従って , ファンクタ・オプジェクトを受け取る Functor のコンストラクタは , 該当 ファンクタの型でパラメータ化されたテンプレートとなります : template く typename R, class TList> c1ass Functor . 上記と同じ public : template く class Fun> Functor(const Fun& fun) ; 117 template く class ParentFunctor , typename Fun> て全てのものをまとめて扱うことができます : 体化自身をテンプレート・パラメータにします。内部で typedef を用いているため , 単独のパラメータによっ FunctorHand1er に対してたくさんのテンプレート・パラメータを定義しなくても良いよう , Functor の実 できます。 転送します。これには , 前のセクションで operator ( ) を正しく実装するために用いたトリックを用いることが ト FunctorHand1er が必要となります。このクラスは Fun 型のオプジェクトを格納し , operator() をそれに このコンストラクタを実装するには , FunctorImp1 く R, TList > から導出を行った簡単なクラス・テンプレー FunctorHand1er* C10ne ( ) const FunctorHand1er(const Fun& fun) typedef typename ParentFunctor : : Resu1tType Resu1tType ; public : typename ParentFunctor : typename ParentFunctor : :Resu1tType , public FunctorImp1 class FunctorHand1er :ParmList : fun-(fun) { } return fun-()l , (2) ; typename ParentFunctor : : parm2 p2) Resu1tType operator() (typename ParentFunctor : :Parm1 pl , return fun-(pl) ; ResuItType operator() (typename ParentFunctor: :Parm1 pl) return fun— ( ) ; Resu1tType operator() ( ) { return new FunctorHand1er(*this) ; }
6.12 総括 6.12 総括 この章の紹介部分では , C+ + を用いた最も一般的な SingIet 。 n の実装を解説しています。言語サポートが豊 165 ト・パラメータ ( 利用者の型とポリシー毎のパラメータ ) を用いて SingIetonH01der クラス・テンプレートに とを確認しています。そして , こういった設計上の選択を全て組み合わせて網羅できるよう , 4 つのテンプレー へと分解しています。その結果 , SingIeton には , 生成 , 寿命 , スレッド・モデルという 3 つのポリシーがあるこ 最後に , ポリシーを定義する際に利用できる , 様々なバリエーションを整理 , 分類し , Singleton をポリシー パターンは , スレッド・セーフな S ⅲ gleton を実装する上で重宝するパターンです。 Singleton デザイン・パターンには , スレッドに関する深刻な問題が存在します。 Double-Checked Locking ぞれ異なった長所と短所を持ち合わせています。 る Singleton, Phoenix Singleton, 寿命を指定する Singleton, 「リークする」 Singleton です。これらは , それ 破棄後のアクセスに対しては , 4 つの主要なバリエーションを考察しています。つまり , コンパイラ制御によ 全ての SingIeton が実装するべき機能です。 破棄後のアクセスを検出することは , 容易であり , コストもかかりません。こういった死んだ参照の検出は , Singleton の寿命 , 特にその破棄の管理です。 富に存在するため , Singleton によって複数の実体化を抑止することは比較的容易です。最も複雑な問題は , 6.13 Sing1etonH01der クラス・テンプレートのまとめ ポリシーを持ち込むようにしたわけです。 ・ Sing1etonH01der の宣言は以下の通りです : template く class T, template く class> class CreationP01icy template く class> class LifetimeP01icy template く class> class ThreadingMode1 class Sing1etonH01der; CreateUsingNew , Defau1tLifetime , Sing1eThreaded ・ Sing1etonH01der の実体化は , あなたのクラスを第 1 テンプレート・パラメータとして引き渡します。 ⅱス 計上のバリエーションは , その他 3 つのパラメータを組み合わせることによって選択します。例えば : class MyC1ass { typedef Sing1eton く MyC1ass , CreateStatic> MySing1eC1ass ; デフォルト・コンストラクタを定義しなければなりません。さもなければ , creator ポリシーとして用意さ れている実装以外の creat 。 r を引き渡さなければなりません。 ・カプセル化された 3 つのポリシー実装は表 6.1 で解説しています。要求を満足している限り , あなた自身の ポリシー・クラスを追加することも可能です。
296 のようなコードは : static void Fun(); namespace void Fun() ; 匿名のネームスペースを用いると , 以下のように記述することができるのです : 第 11 章 マルチメソッド 型でないテンプレート引数に対して関数へのポインタを引き渡すことができるということは , もはやこういっ たものをマップ中に格納しなくても良いということを意味しているのです。完璧に理解するにはこういった観点 が必要不可欠です。関数へのポインタを格納しなくても良い理由は , コンパイラがそれに関する静的な知識を 持っているからです。このため , コンパイラはトランポリン・コード中に関数のアドレスを直接書き込むことが できるのです。 BasicDispatcher をバックエンドとして用いる新たなクラスを実装し , そこにこのアイディアを組み込んで みましよう。新たなクラス FnDispatcher は , 関数 ( ファンクタは対象外です ) のディスパッチに特化したも のです。そして FnDispatcher は , BasicDispatcher を private で保持し , 適切な転送関数を提供します。 FnDispatcher : : Add テンプレート関数は , 3 つのテンプレート・パラメータを受け取ります。最初の 2 つは , ディスパッチ登録を行う左手側と右手側の型 (ConcreteLhs と ConcreteRhs) です。 3 つ目のテンプレート・ パラメータ (callback) は , 関数へのポインタです。追加された FnDispatcher: :Add によって , 以前に定義 した 2 つのテンプレート・パラメータのみを用いたテンプレート Add がオーバーロードされます : template く class BaseLhs , class BaseRhs = BaseLhs , Resu1tType = void> class FnDispatcher BaseDispatcher く BaseLhs , BaseRhs , Resu1tType> backEnd public : template く class ConcreteLhs , class ConcreteRhs , Resu1tType (*callback) (ConcreteLhs& , ConcreteRhsD > void Add() / / 第 2 章を参照 struct Loca1 static Resu1tType Tramp01ine(BaseLhs& lhs , return callback( dynamic—cast く ConcreteLhs&> ( 1 れ s ) , dynamic-cast く ConcreteRhs&> (rhs) ) ; &LocaI : :TrampoIine) ; return backEnd— . Add く ConcreteLhs , ConcreteRhs> ( BaseRhs& rhs)
288 のメリットは , 速度 ( 階層中に多くの型が存在しない場合 ) と非侵入性です。 には , 階層の修正を行う必要がないのです。 11.5 カまかせのディスパッチャにおける対称性 第 11 章マルチメソッド StaticDispatcher を用いる場合 2 つの図形の交差領域を網掛けする際 , 四角形が楕円に重なっている状態と楕円が四角形に重なっている状態 を区別する必要のある場合があります。それとは逆に , 楕円と四角形の交差領域を , 重なっている状態とは関係 なく , 同じように網掛けする必要のある場合もあります。後者の場合に必要となるのが , 引き渡した引数の順序 を意識しないマルチメソッド , すなわち対称型マルチメソッドです。 対称性は , 2 つのパラメータ型が同一である ( 私たちの例では BaseLhs が BaseRhs と同じであり , TypesLhs が TypesRhs と同じである ) 場合にのみ適用されます。 こまでで定義したカまかせの StaticDispatcher は , 非対称 , つまり , 対称型マルチメソッドの組み込み サポートを提供していません。例えば , 以下のクラスを定義したと考えて下さい・ class HatchingExecutor public : void Fire (RectangIe& , RectangIe&) ; void Fire (RectangIe& , EIIipseD ; ー・ / 、ンドラ / / 工ラ void OnError (Shape& , Shape&) ; typedef StaticDispatcher く HatchingExecutor , Shape , TYPELIST_3(Rectang1e , E11ipse , P01y) HatchingDispatcher ; この HatchingDispatcher に対して , 左手側のパラメータとして EIIipse, 右手側のパラメータとして Rectang1e を引き渡した場合 , Fire は呼び出されません。 HatchingExecutor の観点から見た場合 , どちらが 先でどちらが後かは関係ないものの , HatchingDispatcher は特定の順序でオプジェクトを引き渡すことを要 求するのです。 逆順の引数でオーバーロードを行い , 適切な関数に転送することによって , 利用者側のコードで対称性を作り 出すことは可能です : class HatchingExecutor public : void Fire (Rectang1e& , E11ipse&) ; / / 対称性の保証 void Fire (E11ipse& lhs , Rectang1e& rhs)
134 5.15 総括 第 5 章汎用のファンクタ C+ + における良いライプラリというものは , それを記述することよりも使うことの方がずっと簡単です ( そう あるべきです ) 。一方 , ライプラリの記述はとても面白いものです。実装に関する詳細全てを振り返ってみるこ とで , ジェネリックなコードを記述する際におけるいくつかの教訓を得ることができます : ・型が出てきた場合 , テンプレート化によって後に延ばします。また , ジェネリックなものにします。 FunctorHand1er および MemFunHand1er は , テンプレートを用いることによって型の情報が必要となるタ イミングを遅らせ , 様々な利点を作り出しています。関数へのポインタに対するコストは掛かりません。も たらされるものと比べた場合 , コードは驚くほど小さなものとなります。こういったことは全て , テンプ レートの使用と , 可能な限りコンパイラに型を推論させることによって実現できるのです。 ファースト・クラスのセマンティックスを推し進めます。 Fu Ⅱ ctorlmpl ポインタのみを用いて操作を行う としたら , 信じられないほど頭の痛いことになるでしよう。バインドとチェイニングを用いて実装すること を考えて下さい。 簡潔さという利点を得るためには , 賢いテクニックを適用するべきなのです。つまるところ , テンプレート , 継承 , バインド , メモリといった全ての考察から , 簡潔で , 容易に利用でき , 極めて明快なライプラリを作り出 すことができるというわけです。 早い話が , Functor は , 関数 , ファンクタ , あるいはメンバ関数への遅延呼び出しです。 Functor は , 呼び出 されるものを保存し , それを起動するための operat 。 r ( ) を公開するわけです。それでは , まとめを見てみるこ とにしましよう。 5.16 Functor のまとめ ・ Functor は , 15 引数までの呼び出しを表現することができるテンプレートです。最初のパラメータは戻り 型で , 2 番目はパラメータ型を保持したタイプリストです。 3 番目のテンプレート・パラメータによって Functor が使用するアロケータのスレッド・モデルを指定します。タイプリストの詳細については第 3 章 を , スレッド・モデルの詳細については付録を , 小規模オプジェクトの割り当てについては第 4 章を参照し て下さい。 ・ Functor は , 以下のコードで例示されているように , 関数 , またはファンクタ , または別の Functor, また はオプジェクトへのポインタとメソッドへのポインタによって初期化します : VOid Function(int) ; struct SomeFunctor void operator() (int) ; struct SomeC1ass
8.6 こまかい点 private : 工 dentif ierType unknownld- protected: 223 StaticProductType* OnUnknownType (const IdentifierType& id) throw Exception(id) ; FactoryError の他の実装として , 型識別子を検索して何らかの有効なオプジェクトへのポインタを返した り , null ポインタを返したり ( 例外の使用が望ましくない場合 ) , 何らかの例外オプジェクトをスローしたり , プログラムを終了させるといったことが考えられます。 FactoryEr て。ての新たな実装を定義して , Factory の 4 番目の引数として指定することにより , 動作を微調整することができるのです。 8.6 こまかい点 Loki の Factory 実装では , 実際は std : : map を使用していません。 Fact0 て y の使用パターンを考えると , 挿 入の頻度は低く , 検索の頻度が高い場合に最適化された AssocVector というマップの代替を用いた方が良い結 果を生むのです。 AssocVector は , 第 11 章で詳細に解説しています。 Factory の初期設計では , テンプレート・パラメータにすることによってマップ型をカスタマイズできるよう にしていました。しかしほとんどの場合 , Ass 。 cvect 。 r で十分要求を満足できたのです。また , template テン プレート・パラメータとして標準コンテナを用いることは標準として確立されていませんでした。なぜなら , 標 準コンテナの実装者は , デフォルトを提供する限り , 様々なテンプレート引数を自由に追加することができたか らです。 それでは , productcreator テンプレート・パラメータに注力することにしましよう。その主要な要求は , 関 数としての動作を行い ( 引数無しの operator() を受け付ける ) , AbstractProduct* と互換性のあるポインタ を返すというものです。上述の具体的な実装では , pr 。 ductc て eator は単なる関数へのポインタでした。私たち のニーズが , Ⅱ ew の起動によってオプジェクトを生成する ( 最も一般的なケースです ) というものであれば , AbstractProduct* (*PointerTOFunction) ( ) アスタリスクの直後に名前を補記してみましよう。以下のようになります : この型は名前が存在していないため , ちょっと気持ちの悪いものに見えるかもしれません。このため , 括弧内の AbstractProduct* ( * ) ( ) れで十分でしよう。従って , ProductCreator のデフォルト型は以下のようになります : タ , すなわち Functor く AbstractProduct*> というものもあります。これを使用すると大きな柔軟性を手に入 第 5 章と言えば , ProductC て eator として Facto て y に引き渡すことができる面白いテンプレート・パラメー 察を参照して下さい。 判ると思います。これでもまだ馴染みが無いというのであれば , 第 5 章を参照して関数へのポインタに関する考 これで , 引数を受け取らない関数へのポインタ型であり , AbstractProduct へのポインタを返すということが