2. "Implementing QuantLib"の和訳

Appendix A   Odds and Ends 周辺の話題

A.6   Utilities   ユーティリティークラス

QuantLibでは、金融に関する概念以外の概念も、いくつかのクラスや関数によってオブジェクトモデル化されています。これらは、ライブラリー全体の土台を築くのに使われるボルトやナットのような役割をしています。その内のいくつかについて、このセクションで説明します。 

 

A.6.1   Smart PointersとHandles : スマートポインターとハンドル

実行時 polymorphism (多相性)を使っている事により、(大半とは言いませんが)多くのオブジェクトが Heapメモリー上に領域を割り当てられています。この事は、メモリー管理の問題を発生させます。他のプログラム言語では、その役割は備え付けの Garbage Collection ( Heap上の不要なメモリー領域を管理し消去する機能)が行っていますが、C++ では開発者自身で管理する必要があります。 

メモリー管理に関しては、殆どが過去の問題で、現時点で多くの問題が残っている訳ではありません。この問題への対応はやっかいなので(特に、例外処理が発生した場合)、マニュアル管理ではなく、できるだけ自動的にできるような方法を探してきました。 

C++ 開発者にとって、この問題と戦う為の最善の武器は、スマートポインターです。これは、C++ に備え付けのポインターと同じ機能を持つと同時に、ポインターが指し示すオブジェクト用に、メモリーの生成や維持管理を行い、また不要になれば破棄するという動作まで行っています。このような機能を持つクラスの実装例はたくさんありますが、我々は Boostライブラリーが提供しているスマートポインターを選択しました (特に shared_ptr で、今では ANSI/ISO の C++ 標準ライブラリーにも含まれています)。このポインターの詳しい説明は Boostライブラリーの文書を見て下さい。QuantLibは、このスマートポインターを使う事により、メモリー管理が完全に自動化されているという点だけ述べておきます。様々なオブジェクトがあちこちで、動的にメモリー配分されていますが、QuantLibの何万行ものコードの中で、ひとつも delete を使っていません。 

“ポインターへのポインター”も、スマートポインターと同じ様な機能(訳注:メモリーの領域取得と破棄を自動的に行う機能)をするクラスを使って用意しています(“ポインターへのポインター”について再確認したい方は、このセクションの最後のAsideをご覧下さい)。我々は、単にスマートポインターへのスマートポインターを使うような選択をしませんでした。仮にそうすると、次のようなコードになりますが、Emacs (訳注: UNIX環境のプログラマーに人気のテキストエディタで、キーボード操作だけで素早くプログラムを書ける)を使ったとしても、長たらしいコードを書くのは面倒です。 

    boost :: shared_ptr <boost :: shared_ptr <YieldTermStructure> >

また、この構文の中の内側のポインターは、動的に配分される必要がありますが、あまり気持ちよくありません。さらに、(訳注:Observableへの監視が、さらに間接的になるので) Observerパターンを実装するのが難しくなります。 

そこで、QuantLibでは、Handleというクラステンプレートを用意しています。その実装内容を下記 Listing A-23 に示しますが、このクラスの機能は、スマートポインター (shart_ptr<Type>) をメンバー変数に持つ“Link”という介在役の内部クラスを利用しています。Handleクラスは、それを使って、Link型インスタンスへのポインター(shared_ptr<Link<Type>>) をメンバー変数として保持し (訳注: この変数がポインターのポインターに該当)、かつそれを使いやすくするためのメソッド群を用意しています。ある Handleインスタンスのコピーは、すべて同じ Linkインスタンスを共有するので、そのリンク先のオブジェクトに変更があった場合でも、それがすべての Handleインスタンスに自動的に反映されます。 

Listing A-23:Handleクラステンプレートの概要 

    template <class Type>
    class Handle {
      protected:
        class Link : public Observable, public Observer {
          public:
            explicit Link(const shared_ptr<Type>& h =
                                         shared_ptr<Type>());
            void linkTo(const shared_ptr<Type>&);
            bool empty() const;
            void update() { notifyObservers(); }
          private:
            shared_ptr<Type>  h_;
        };
        boost :: shared_ptr<Link <Type> >  link_;
      public:
        explicit Handle(const shared_ptr<Type>& h = shared_ptr<Type>());
        const shared_ptr<Type>&  operator->() const;
        const shared_ptr<Type>&  operator*() const;
        bool empty() const;
        operator boost :: shared_ptr<Observable>() const;
    };

    template <class Type>
    class RelinkableHandle : public Handle<Type> {
      public:
        explicit RelinkableHandle(const shared_ptr<Type>& h = shared_ptr<Type>());
        void linkTo(const boost :: shared_ptr<Type>&);
    };

さらに Handleクラスは、shared_ptr<Link<Type>>型の変数によって、他のクラスのObservableとなる機能も与えられています。即ち、Linkクラスは Observer と Observable から派生しており、自分が監視している Observableから(データ更新の)通知を受けた場合、それを、自分を監視している Observerに転送する機能を持ちます。また、自分がポイントしているオブジェクトが入れ替わった場合にも、同様に Observerに通知します。Handleクラスは、この機能を、Linkへのポインターを返す shared_ptr<Observable>( )オペレーターの自動型変換を定義する事によって実現しています。従って、次のような宣言は構文上許されており、期待通りの動作をします。 

	    registerWith(h);

登録された Observerは、Linkインスタンスと(間接的に)それが指し示すオブジェクトから通知を受ける事になります。 

Handleを別のオブジェクトに再リンクする(すなわち、ある Handleインスタンスのコピーも同時にすべて、リンクし直した後の別のオブジェクトを指し示す)方法が、Handleクラスそのものではなく、派生クラスである RelinkableHandleクラスに与えられている点に気づかれたでしょうか。このようにした理由は、どの Handleインスタンスが再リンクの機能を使う事ができ、どの Handleがその機能を使えないようにするかについて、間違えないようにする為です。Handleクラスの典型的な使用例では、Handleインスタンスが生成された後、様々な金融商品オブジェクトや価格エンジン、その他のオブジェクトに渡されて、それらのオブジェクト毎に、その Handleインスタンスのコピーが保存され、使用されます。ここで重要な点は、Handleのコピーを保持しているオブジェクトに Handleが指し示しているポインターの再リンクを、どのような理由があっても、許してはならないという事です (あるいは、そのオブジェクトのインスペクター関数を使って、オブジェクトの外から Handleインスタンスにアクセスできるようにしている場合には、その外からのアクセス元にも同様)。仮にそうしてしまうと、他のオブジェクトにひどい影響を与える可能性があります。(注:そういった影響は、思ったよりも頻繁に起こり得ます。実際に、それでひどい目にあいました) 

再リンクは、オリジナルのHandleインスタンス(Master Handleとでも呼びましょうか)からのみ、変更が許されるべきなのです。 

人間にその管理を任せると間違いを起こしやすいので、我々は、それをコンパイラーに強制させるようにしました。しかし、linkTo( )メソッドを const にした上で、インスペクター関数から、const な Handle を返すような方法では、機能しません。なぜなら Clientコードで Handleインスタンスの non-const なコピーが、簡単に作れてしまうからです。そこで、我々は、linkTo( )メソッドを Handleクラスのインターフェースから取り除き、派生クラスに移しました (訳注:再リンクの機能を、RelinkableHandleクラスという特別な派生クラスにのみ許可している)。C++ にある型変換の仕組みを使えば、これでうまくいきます。まず、マスターHandle の役割を持つインスタンスを、RelinkableHandleクラスを使って生成します。そのインスタンスを、同じポイント先のデータを必要とする他のオブジェクトに渡します。その際、派生クラスからベースクラスへの自動型変換が働き、そのオブジェクトは完全に機能する Handleインスタンスのコピーを受け取ります。一方で、Handleインスタンスのコピーがインスペクター関数から返された場合、それを RelinkableHandle へ Down-Castする方法はありません。(訳注:その結果、Link先のポインターの変更は RelinkableHandleのマスターインスタンスでしか行えないことになる) 

 

<  Aside : Pointer Semantics ポインターに関するC++構文上の決まり > 

あるクラスのインスタンスがポインターのコピーを保持する場合、そのインスタンスはポインターが指し示す先の現在の値にアクセスする事ができます。次のコードを見て下さい。 

    class Foo {
	    int* p;
	public:
	    Foo(int* p) : p(p) {}
	    int value() { return *p; }
    };
    int i=42;
    int *p = &i;
    Foo f(p);
    cout << f.value(); // will print 42
    i++;
    cout << f.value(); // will print 43 

しかし、インスタンスの変数に代入されたポインターは、もともと当初のポインターのコピーになりますが、その当初のポインターがインスタンスの外で変更されたとしても、クラスインスタンスに保持されているポインターはその影響を受けません。 

	int i=42, j=0;
	int *p = &i;
	Foo f(p);
	cout << f.value(); // will print 42
	p = &j;
	cout << f.value(); // will still print 42 

通常、これの解決策は、間接的アクセスをもう一段階加える事です。Fooクラスを修正して、ポインターのポインターを格納するようにすると、以下のように、上記2種類の変更の両方に対応可能です。 

	int i=42, j=0;
	int *p = &i;
	int **pp = &p;
	Foo f(pp);
	cout << f.value(); // will print 42
	i++;
	cout << f.value(); // will print 43
	p = &j;
	cout << f.value(); // will print 0 

 

A.6.2  Exceptions : 例外処理

QuantLibライブラリーの中には、入力値の条件チェックを行うべき場所が至る所にあります。このチェックの方法については、 

	if (i >= v.size())
		throw Error("index out of range");

という風にやるのでは無く、次の構文のようにチェックの意図がより明確に解るような方法を取りたいと考えました。 

	require(i < v.size(), "index out of range");

この構文であれば、条件が満たされない事をチェックするのではなく条件が満たされる事を要求するような表現になります。さらにそれ自体の意味が自明な require や ensure や assertという単語を使って、チェックの意図(入力値のチェックなのか、出力値のチェックなのか、プログラムのエラーチェックなのか)が解るようなプログラムコードが書けます。 

我々は、そのような syntax(構文)を、マクロを使って提供しています。読者の方が“そんなの止めてくれ”と言っているのが聞こえます。その通り、マクロは良くないと思われているし、実際にこれによって若干の問題も発生しています。それについては下記で説明します。しかしこの場合は、関数を使う方がより大きなデメリットをもたらします。なぜなら関数は、すべての引数を評価するからです。次のような、やや複雑な例外処理のメッセージを生成する場合が多々あります。 

	require(i < v.size(),
		"index " + to_string(i) + " out of range");

この場合、もし require( )が関数であるなら、(2つめの引数である)メッセージの部分は、条件が満たされようが無かろうが、必ず生成されます。そうすると、場合によっては許容できないほどのパフォーマンス悪化をもたらします。もし同じ構文をマクロで作ると、マクロの部分は実際には次のような構文で置き換えられます。 

	if ( !( i < v.size() ) )
		throw Error ("index " + to_string(i) + " out of range");

こうすれば、例外処理のメッセージは、条件が満たされなかった場合のみ生成されます。 

次の Listing A-24 は、そういったマクロのひとつ QL_REQUIRE の現バージョンを示しています。その他のマクロも同じ様に定義されています。 

Listing A-24:QL_REQUIREマクロの定義 

    #define QL_REQUIRE(condition, message) \
    if (!(condition)) { \
        std::ostringstream _ql_msg_stream; \
        _ql_msg_stream << message; \
        throw QuantLib::Error(__FILE__,__LINE__, \
                              BOOST_CURRENT_FUNCTION,
                              _ql_msg_stream.str()); \
    } else 

この定義の方法を使えば、エラー時に、さらにいくつかの警告メッセージが出されます。ひとつ目は、メッセージを生成するのに標準ライブラリーの ostringstream( )を使っているので、次のような構文が使えます。 

   QL_REQUIRE(i < v.size(),
              "index " << i << " out of range"); 

(この方法がどのように動作するか理解する為に、マクロの本体を一部修正して動作確認してみて下さい。) ふたつ目は、例外処理に飛ぶ場合、Errorインスタンスに、今走っている関数の名前と例外処理に飛んだコードの line とファイル名が渡されます。コンパイル時のフラッグ設定によりますが、この情報もエラーメッセージの中に含める事が可能で、プログラマーの問題発見の参考になります。QuantLibライブラリーでのデフォールトの設定は、ユーザーにとっての便益があまり無いので、この情報をメッセージに含めないようになっています。最後に、マクロ構文の最後になぜ ”else” が付いているか疑問に思うでしょう。マクロに共通する落とし穴で、lexical scope の欠如と呼ばれています。else以下は、次のように何らかのプログラムコードを追加する必要があります。 

    if (someCondition())
        QL_REQUIRE(i < v.size(), "index out of bounds");
    else
        doSomethingElse(); 

マクロの中に else が含まれていなかったとすると、上記のコードは意図した通りに動作しません。else をマクロの外でプログラムコードの中に入れた時、マクロ中の if 文に対する else と組み合わされ、次のようなコードが書かれたと解釈されてしまいます。 

    if (someCondition()) {
        if (!(i < v.size()))
            throw Error("index out of bounds");
        else
            doSomethingElse();
    } 

これは、意図した動作ではありません。 

最後の注意点として、これらのマクロの不利益な点についても説明しなければなりません。現状のマクロの記述では、例外処理に飛ぶと、引数に含まれているメッセージしか返す事が出来ません。その他のチェックすべきデータのインスペクター関数が全く定義されていません。例えば、インデックスが“範囲外”とのメッセージの中にそのインデックスを含める事が出来ますが、例外処理のプロセスの中にそのインデックスを integer として返すメソッドがありません。従って、その情報はユーザーが Displayで見る事は出来ますが、例外処理後にエラーをリカバリーするプログラムコードの中で使う事が出来ません。だれかがメッセージからその情報を切り取る以外は。しかし、そのメリットはそのコードを書く手間に見合いません。従って、今の所、解決策はありません。もし読者の中で良いアイデアをお持ちなら、そのコードを送って下さい。 

 

A.6.3   Disposable Objects (move semantics の代用)

Disposableクラステンプレートは、C++98 準拠のコードで、move semantics を実現する為の試みでした。(訳注:move semanticsについては ”右辺値参照・ムーブセマンティクス”) そのアイデアを提唱した人の功績を称える為、そのアイデアを取ってきた原典を紹介します。それはAndrei Alexandrescu氏の論文(”Move Constructors” In C/C++ Users Journal, February 2003 )で、そこでは、関数が値を返す際に一時的に生成されるオブジェクトのコピーを省略する方法を提示しました。 

基本的なアイデアは、彼の文献が発表された当時、すでに広まりつつあったものですが、最終的に C++11 で正式に取り入れられました。すなわち、一時的に生成されたオブジェクト(右辺値)を、他のオブジェクト(左辺値)にコピーする場合、場合によっては、その一時オブジェクトを代入先と交換する方が、プロセス効率が上がる場合があります。例えば、一時的に生成された配列変数を動かしたい時 (要素をすべて別の配列変数にすべて移す場合) どうしますか? 新しい配列変数を作って、一時的な配列変数が示すPointee(配列の中身)の先頭アドレスをコピーする方が、配列変数の要素をすべてコピーするより早いですよね。新しい C++ では、rvalue reference(右辺値参照) の概念を使って move semantics をサポートしています。(Hinnant et al “A Brief Introduction to Rvalue References.” C++ Standards Committee Paper N2027, 2006. ) 

コンパイラーは、一時的に生成されるオブジェクトの生成消去のタイミングが分っています。それを利用すれば (C++11準拠の) std::move を使って、一時的に生成されたオブジェクトを、別のオブジェクトに移す事ができます。ところが、我々の実装方法は、次の Listing A-25 にあるように、そのようなコンパイラーのサポートを使わずに実現しようとするものです。その結果どうなったか、すぐ後に説明します。 

Listing A-25 :Disposable クラステンプレートの実装 

    template <class T> class Disposable : public T {
	public:
	    Disposable(T& t) { 
	    	this->swap(t);
	    }
	    Disposable(const Disposable<T>& t) : T() {
	    	this->swap(const_cast<Disposable<T>&>(t));
	    }
	    Disposable<T>& operator=(const Disposable<T>& t) {
	    	this->swap(const_cast<Disposable<T>&>(t));
	    	return *this;
	    }
    };

このクラスそのものに、あまり注目すべき所はありません。このクラスの動作は、swap( )メソッドを実装する際に使われるテンプレート引数に依存します。このメソッドを使って、引数のインスタンスの中に含まれるデータを(望むべくは効率よく)コピーではなく、スワップします。コンストラクターと代入演算子はすべて、この swap( )メソッドを使って、インスタンスの中のデータを、コピーせずに新しいオブジェクトに移動させます。  

Disposableインスタンスを別の Disposableインスタンスを使って構築する場合は、const参照を使って、構築元のオブジェクトを受取ります。なぜなら引数のオブジェクトを、一時変数のままにしておく必要があるからです(実際に Disposableオブジェクトの殆どは、一時変数です)。従って、実際に swap( )メソッドを起動した時、Disposable の中身を使う必要があるので、そこで const_cast を使う必要があります。(訳注:const_cast により引数の Disposable の const を外し、内容を変更可能にする為) Disposable を Disposable以外のオブジェクトから作る場合は、反対にそれを non-const な参照として取ります。これは、破壊的な型変換を起こしたり、使えると思ったオブジェクトが空っぽだった状況に陥るのを防ぐ為です。この事はしかし、デメリットもあります。それについては、少し後で触れます。 

次の Listing A-26 は Disposableを(テンプレート引数で特定された)クラスに改造する方法を示しています。ここでは Arrayクラスを例に使っています。 

Listing A-26 : Arrayクラスを使った、Disposableクラステンプレートの使用法 

    Array :: Array (const Disposable<Array>&  from) : data_( (Real*)(0) ), n_(0) {
	 swap ( const_cast <Disposable<Array>&>(from) );
    }
    Array& Array :: operator= (const Disposable<Array>& from) {
	 swap(const_cast<Disposable<Array>&>(from));
	 return  *this;
    }
    void  Array :: swap(Array& from) {
	 data_.swap ( from.data_);
	 std :: swap (n_, from.n_ );
    } 

ご覧の通り、Disposableを引数で取る、コンストラクターおよび代入演算子を追加で定義する必要があります。( C++11では、これらは、rvalue reference(右辺値参照)を取る moveコンストラクターと move代入演算子になります) それと同時に、コンストラクターと代入演算子の中で使われる swap( )メソッドも実装しなければなりません。ここでもコンストラクターは、一時変数を拘束する為に、引数を const参照として取り、後で const_cast して const を外しています。しかし、今思えば、引数をコピーで取るような、もうひとつの効率的なswap( )メソッドを追加してもよかったかも知れません。 

最後に、関数からの返される値としてDisposable <class > を使う方法についてです。以下のコードをご覧下さい。 

    Disposable <Array>  ones(Size n) {
	Array  result(n, 1.0);
	return  result;
    }
    Array  a = ones(10); 

ones( )メソッドが Array型を返すと、それが Disposable<Array>に型変換され、それをさらに Array型の変数 a に代入すると、その内容が a とスワップされます。ここで、先ほど Disposableコンストラクターが安全に non-const の参照を取る場合のデメリットについて述べて事を思い出して下さい。デメリットとは、一時変数を bind 出来ないという事です。従って、上のコードの ones( ) を以下のように簡略化する事はできません。 (訳注:上のコードでは、関数内で一旦Array型のローカル変数result(左辺値)を生成して、それを返すようにしているが、下記コードでは、Arrayのコンストラクターの返す値(Array型のインスタンスで右辺値) を関数の返値としている) 

    Disposable <Array > ones(Size n) {
	return Array(n, 1.0);
    } 

なぜなら、このコードはコンパイルできません。その結果、より冗長な構文を強いられ、かつそのArray配列に変数名を割り当てなければなりません。 

(注:実際には強いられている訳ではありません。例えば 上のコードで、Array()を返すのではなく、Disposable<Array> (Array(n,10)) を返すようにすれば、ローカル変数をわざわざ生成する必要はありません。しかし、これではローカル変数を生成するよりも、さらに判りにくいでしょう。) 

現時点では (C++11以降に準拠すれば)、もちろん右辺値参照と moveコンストラクターを使って、以上のような事 (訳注:わざわざ Disposable<>クラステンプレートを作り、さらに各クラスでコンストラクターと代入演算子をオーバーロードする作業) を忘れ去る事ができます。さらに正直に申し上げると、Disposable< > はコンパイラーの邪魔をして、意図したメリットよりもデメリットの方が多いのではと疑っています。実は、最新の C++ に準拠すれば、上記のようなシンプルなコードで、かつ抽象化のペナルティーを避ける方法があるのをご存知ですか? それは、次の様なものです。 

    Array ones(Size n) {
	return Array(n, 1.0);
    }
    Array a = ones(10); 

C++17では、Arrayを返す際や、代入する際に行われていた(一次変数への)コピーの動作が、実は省略されています (すなわち、コンパイラーによって、代入先の変数のメモリー領域を、代入元の領域にあらかじめ確保するようなコードが生成されている)。最新のコンパイラーは、この動作が C++ 準拠と決まる前に、既にこういった省力化を行っていました。コンパイラーが自動的に行ってくれるこの動作は、RVO (Return Value Optimization) と呼ばれています。しかし乍ら、Disposable< > を使うと、コンパイラーが行うこの自動的な動作を妨げ、返って処理速度を遅くしてしまう可能性があります。 

 

 

<ライセンス表示>

QuantLibのソースコードを使う場合は、ライセンス表示とDisclaimerの表示が義務付けられているので、添付します。   ライセンス

目次

Page Top に戻る

// // //