Boost.Contract その1 - C++ Advent Calender 2012
これは C++ Advent Calender 2012 の3日目の記事です。
ーBoost.Contract
0.4.1が最新です。(2012/12/03現在)
はい、契約だからってま○どマギネタとか出てくるんじゃないかなーって期待していたそこのアナタ!残念ながら使い回されすぎて使いにくいため、そういったものは出てきませんのであしからず。
Introduction
今回は、今年9月頃に採択されたBoost.Contractについてご紹介します。
そもそもContract Programming(Design by Contract - DbC)は、はじめEiffel,Spec#など*1で取り入れられていたものです。契約プログラミングの大体の説明はWikipediaに任せるとして、それをC++ではどうかけるのか、これからまとめていきたいと思います。
Boost.ContractのTutorialがとてもわかり易かったので、それを参考にしたりまんまコピってきたりしてサンプルコードを作っています。
基本構文
事前条件(precondition)
まず、下の関数を見てみましょう。
#include <array> std::array<int, 5> arr; int func( const int index ) // precondition: { return arr[index]++; }
簡単ですね、intを引数にとって、要素番号indexの値を後置インクリメントして返してるだけです。
ここでの事前条件は何でしょうか。そうです、0≦index<5であることが条件ですね。(要素外アクセス)
さて、どう表現したものか…これぐらい単純ならifでチェックしてもいいぐらいですが、せっかくだからfuncさんと契約を結んでみましょう。
#include <array> std::array<int, 5> arr; CONTRACT_FUNCTION( int (func) ( (const int) index ) precondition( 0 <= index, index < 5 ) ) { return arr[index]++; } int main() { func( 0 ); }
これでfuncとの間につながりが芽生えましたね。契約成功です。
ちなみに、契約を破ったものには何が訪れるのでしょうか…どきどき
int main() { func( 5 ); //func( 0 )から変更してみた }
precondition number 2 "index < 5" failed: file "ここに契約違反したファイルの名前が入ります。", 行番号
はい、上記のようにコンソールに出力されて落ちます。契約を破ったものにはassertで厳しい罰則がかせられるということですね。
さぁ、コードの説明に入ります。
CONTRACT_FUNCTION( int (func) ( const int index ) precondition( 0 <= index, index < 5 ) ) { ... }
今回のように関数と契約したい場合はCONTRACT_FUNCTIONの儀が必要です。上記の用に包んでおけば大丈夫です。
CONTRACT_FUNCTIONの中で、関数の戻り値や名前、引数を決定しています。普段の関数と同じように、戻り値、関数名、引数という順番ですね。関数名はfuncですが、CONTRACTのマクロに投げるために(func)と書かなければなりません。引数はここでは普段通りに書いています。
ここでメインのpreconditionが登場です。ここに、事前条件(呼び出し側が保証しなければならないこと)をづらづらと書き連ねていくわけです。書式は、
precondition( precondition1, precondition2, ... )
といった具合に、複数指定する場合はコンマで区切ります。
precondition( 0 <= index, index < 5 ) は引数indexが0以上5未満、すなわち関数呼び出し側で引数に0以上5未満の整数を渡すことを保証しなければならないということです。
また、オーバーロードについては普段のC++プログラミングと同じようにできます(実際のコードはここでは割愛します)。
デフォルト引数は普段通りに
CONTRACT_FUNCTION( int (func) ( const int index=0 ) ... )
と書いてはいけません。マクロで死にます。
デフォルト引数の書き方はこうです。
CONTRACT_FUNCTION( int (func) ( const int index, default 0 ) // int func( const int index=0 ) にあたる ... )
デフォルト指定したい引数の「直後」にコンマで区切って default default-value とすればいいです。複数個の場合は
CONTRACT_FUNCTION( void (foo) ( const int bar, default 0, const int hoge, default 1 ) ... )
と書きます。
配列と関数オブジェクトも、ライブラリのマクロで死ぬのでそのままだとだめです。使いたいならtypedefしてあげる必要があります。
typedef int array[10]; typedef int (*funcptr) ( int ); CONTRACT_FUNCTION( int (func) ( (array), (funcptr) ) // int (func) ( int array[10], int (*funcptr)( int ) ) はだめです ... )
Warning!!
ここで注意が幾つかあります。
- CONTRACT_CONFIG_FUNCTION_ARITY_MAXマクロ
これ、何かというとCONTRACT_FUNCTION内の関数に渡せる最大のパラメータ数を表しているものです。が、デフォルトでは#define CONTRACT_CONFIG_FUNCTION_ARITY_MAX 5となっており、最大数が5になっています。つまり6個以上渡したいならそのままだと引っかかります。お気をつけて…
- 戻り値の型、及び引数の型
先程の例の一部を抜粋。
int (func) ( const int index )
さて、ここで戻り値の型をintからint&に変えるとどうなるでしょうか。ここでわざわざ聞くということは大体予想がつくと思いますが、エラーで落ちてしまいます。tokenにa-Zと0〜9以外の文字が含まれているとライブラリのマクロでうまいこといかないようですね。
もしint&と書きたければどう書けばいいのかというと、
(int&) (func) ( const int index ) // もし引数の const int を int& に変えたいと思った場合も、 // ( (int&) index )と書いてね!
こうです。中括弧でくくります。こうすればエラーに悩まされることもなく動きます。やったね!
“The function result and parameter types must be wrapped within parenthesis unless they are fundamental types containing no symbol ( symbol are tokens different from the alphanumeric tokens a-z, A-Z, 0-9 ).“ - Boost.Contractの説明文より引用
事後条件(postcondition)
次に、事後条件もこの勢いで行っちゃいましょう。
std::array<int, 5> arr; CONTRACT_FUNCTION( int (func) ( (const int) index ) precondition( 0 <= index, index < 5 ) postcondition( auto result = return, auto old_value = CONTRACT_OLDOF arr[index], result == old_value ) ) { return arr[index]++; }
こうして僕とfuncとの間により強い契約が結ばれた…
今回問題になるのは
postcondition( auto result = return, auto old_value = CONTRACT_OLDOF arr[index], result == old_value )
の部分です。上から見ていきますね。気をつけて欲しいのが precondition と postcondition の間にコンマが要らないことです。
postcondition の中の一つ一つはpreconditionと同じようにコンマで区切ればいいです。
auto ってのは普段の auto と同じで、型推論です。
auto result = return ですが、これは戻り値を変数 result に格納しています。戻り値を格納するときは return です。 =return なんて普段書かないので気持ち悪いかもしれませんが、そういう決まりなので書いてください。
次、結構大事な CONTRACT_OLDOF です。この子の後に関数が呼び出される前の値を保存したい式を書きます。*2
auto old_variable = CONTRACT_OLDOF <span class="deco" style="font-style:italic;">old_expression</span>
ここでは、 old_value に関数が呼ばれる前の古い arr[index] の値が保存されます。
result == old_value は、先ほど宣言した result と old_value 戻り値と元の値が同じであることを事後条件として契約しています。
ちなみに、変数名は自由にしてもらっていいです。
Warning!!
またです。すみません。とはいってもここはそんなに大層なことではないです。読み飛ばしてもいいです。
- CONTRACT_OLDOFで保存できる最大数
ここでも最大数が設けられていまして、こっちはpreconditionよりも大事です。複雑な契約をしようとすると、こいつで引っかかることがあります。
CONTRACT_CONFIG_OLDOF_MAXマクロで最大数が決まっています。デフォルトは5です。
そもそも複雑になってくるとContractの範疇じゃなくなってくるかもしれませんが。
- 保存する変数はConstantCopyConstructibleで無くてはならない
そのままです。postconditionで保存するときコピーするので当たり前ですね。
ちなみに、 Free Function での precondition, postcondition の評価順は以下のようになっています。
- preconditionのチェック
- 関数の本体を実行
- postconditionのチェック
不変条件(invariant)
invariantは、そのオブジェクトの常に変わらない性質を契約します。
Boost.ContractのTutorialがわかり易かったのでここは引用します。
class ivector { public: typedef std::vector<int>::size_type size_type; public: explicit ivector( size_type count ) : vector_( count ) {} virtual ~ivector() {} public: void push_back( const int value ) { vector_.push_back( value ); } private: std::vector<int> vector_; };
実用性は無視して、こんなクラスがあったとしましょう。
このクラスの不変条件は何でしょう。すこし列挙してみます。
- vector_ の empty() は size()==0 なら true であること
- size() は常に capacity() 以下であること
- capacity() は常に max_size() 以下であること
他にもあるかもしれませんが、だいたいこんなもんでしょう。
このクラスとそういう契約をしたい場合は、下記のように書きます。
CONTRACT_CLASS( class (ivector) ) { CONTRACT_CLASS_INVARIANT( empty() == ( size() == 0 ), size() <= capacity(), capacity() <= max_size() ) public: typedef std::vector<int>::size_type size_type; typedef std::vector<int>::const_reference const_reference; typedef std::vector<int>::const_iterator const_iterator; public: ivector( size_type count ) : vector_( count ) {} virtual ~ivector() {} public: bool empty ( void ) const { return vector_.empty(); } size_type size ( void ) const { return vector_.size(); } size_type max_size ( void ) const { return vector_.max_size(); } size_type capacity ( void ) const { return vector_.capacity(); } const_reference back ( void ) const { return vector_.back(); } const_iterator begin ( void ) const { return vector_.begin(); } const_iterator end ( void ) const { return vector_.end(); } void push_back( const int value ) { vector_.push_back( value ); } private: std::vector<int> vector_;
CONTRACT_CLASSマクロは、契約したいクラスに使います。CONTRACT_FUNCTIONのclass版だと思っていただければそれでいいです。structもこのマクロで同じように契約できます。契約のもとでも、classとstructのデフォルトアクセスレベルの違いは残っています。*3
あと、残念ながら union には対応していません。注意が必要です。
CONTRACT_CLASS_INVARIANTマクロで不変条件を記述できます。ちょっといい忘れていたんですが、precondition, postcondition, CONTRACT_CLASS_INVARIANTマクロ の中の条件式は bool じゃなくてもいい感じでした。 if文と同じかなとおもいます。
CONTRACT_CLASSマクロとCONTRACT_CLASS_INVARIANTマクロはどちらか片方だけではいけません。必ず両方書いてください。また、ひとつのクラスに複数のCONTRACT_CLASS_INVARIANTマクロはかけません。一対の関係です。気をつけてくださいね。
もし、CONTRACT_CLASSマクロは使いたいけどCONTRACT_CLASS_INVARIANTマクロは別に…という人は下記のように書いてください。
CONTRACT_CLASS( class (foo) ) { CONTRACT_CLASS_INVARIANT( void ) }
コンストラクタとデストラクタの契約
さて、見返してみるといろいろ足りませんね。まず、ctorのcountの値をチェックしていません。でもctorなんてかけるのかな…これがかけるんですよ。下のコードを見てください。
CONTRACT_CLASS( class (ivector) ) { ... public: CONTRACT_CONSTRUCTOR( public explicit (ivector) ( (size_type) count ) precondition( count >= 0 ) postcondition( size() == count, boost::algorithm::all_of_equal( begin(), end(), 0 ) ) initialize( vector_( count ) ) ) {} ...
コンストラクタの契約は CONTRACT_CONSTRUCTORマクロ を用いて行います。
CONTRACT_CONSTRUCTORマクロ内でも precondition,postcondition はかけます。
public explicit (ivector) ( (size_type) count )
この部分ですが、Javaライクな書き方になっていますね。これは構文として決まっているので、必ず守ってください。
まとめると、メンバ関数でのCONTRACT_FUNCTIONマクロの構文は
CONTRACT_FUNCTION( access-level (function-name) ( (param-type) param1, ... ) ... )
こうなります。いつもの癖で「public:」とコロンを打ってしまわないように気をつけましょう。
つぎは初期化子です。
ivector( size_type count ) : vector_( count ) {}
CONTRACT_CONSTRUCTOR( public explicit (ivector) ( (size_type) count ) ... initialize( vector_( count ) ) ) {}
初期化子は、initialize( expression1, ... ) という書き方をします。複数個あるときはコンマで区切ってください。あと、いい忘れていましたがprecondition,postcondition,initializeは全部マクロじゃないですよ。
コンストラクタのでの契約の評価順を載せておきますね。
- コンストラクタの事前条件をチェック
- 基底クラスとそのメンバの初期化、あれば初期化リストを処理
- クラスの静的な不変条件をチェック
- コンストラクタ本体の実行
- クラスの静的な不変条件をチェック(たとえ例外が投げられたとしても行われます)
- クラスの非静的な不変条件をチェック(ただし例外が投げられていないことが前提)
- コンストラクタの事後条件をチェック(ただし例外が投げられていないことが前提)
あとでまたやりますが、継承しても大丈夫なようになってます。
次はデストラクタです。
契約前
virtual ~ivector()
{}
契約後
CONTRACT_DESTRUCTOR( public virtual (~ivector) ( void ) ) {}
virtualがついているのでちょっと戸惑うかもしれませんが、大丈夫です。先ほどの explicit と同様にかけます。なので、継承も出来るんですが、それはまた後で。その他は大丈夫ですね。preconditionとpostconditionが書けない理由はわざわざ言及する必要もないので省きます。
ですが、デストラクタも不変条件はチェックされるので、その流れを下に書きます。
- クラスの静的な不変条件のチェック
- クラスの非静的な不変条件のチェック
- デストラクタの本体を実行
- クラスの静的な不変条件のチェック(たとえ例外が投げられたとしても)
- クラスの非静的な不変条件のチェック(例外が投げられていない場合)
こちらもコンストラクタと同様、基底クラスで契約していてもそれもきちんと処理されます。
でも、デストラクタで例外投げるのはやめてくださいね。
しかし、デストラクタは precondition も postcondition もないですし、わざわざ CONTRACT_DESTRUCTORマクロ 使う必要ないのでは?と思う方もいるかもしれませんね。これからいうこと結構重要です。このクラス、CONTRACT_CLASS_INVARIANTマクロありますよね?もしデストラクタを契約しない場合先ほど載せた5つのstepが処理されないことになります。要するに、不変条件のチェックが行われないということです。なので、このデストラクタはたとえ precondition と postcondition がなかったとしても書くべきです。
メンバ関数
契約前
void push_back( const int value ) { vector_.push_back( value ); }
CONTRACT_FUNCTION( public void (push_back) ( int const value ) precondition( size() < max_size() ) postcondition( auto old_size = CONTRACT_OLDOF size(), auto old_capacity = CONTRACT_OLDOF capacity(), size() == old_size + 1, capacity() >= old_capacity, back() == value ) ) { vector_.push_back(value); }
上から順に読んでいっているなら、説明なしでもわかりますね。int const に中括弧がついてないのは keywords に入っているからですね。そのかわりpush_backは囲っています。また、push_back は public なので先頭に public がついていますね。
また、CONTRACT_FUNCTIONマクロで契約したメンバ関数でもクラスの不変条件はチェックされます。その流れを例によって下に書きますね。
- クラスの静的な不変条件のチェック
- クラスの非静的な不変条件のチェック.継承している場合(Subcontractingと言います)は、まず基底クラスの不変条件がチェックされ、それが成立した場合その次に派生クラス側の不変条件が確認されます。これらは非静的なメンバ関数でのみ行われます。volatileなメンバ関数は代わりにクラスのvolatileな不変条件をチェックします。
- 関数の事前条件のチェック(オーバーロードされた基底クラスの関数の事前条件もチェックされ、"どちらかが"成立していれば有効となります)
- 関数の本体を実行
- クラスの静的な不変条件のチェック(例外が投げられていても行われます。)
- クラスの非静的な不変条件のチェック(基底クラスの不変条件もチェックされ、ともに成立していれば有効となります。これは例外が投げられた場合は行われません。)
- 関数の事後条件のチェック(オーバーロードされた基底クラスの関数の事後条件もチェックされ、"どちらも"成立していれば有効となります。これは例外が投げられた場合は行われません。)
英語怪しいので引用元も載せます…
1.Check the static class invariants. 2.Check the non-static class invariants (in logic-and with the base class invariants when subcontracting). These are checked only for non-static member functions. [9] Volatile member functions check volatile class invariants instead. 3.Check the function preconditions (in logic-or with the overridden function preconditions when subcontracting). 4.Execute the function body. 5.Check the static class invariants (even if the body throws an exception). 6.Check the non-static class invariants (in logic-and with the base class invariants when subcontracting). These are checked only for non-static member functions and even if the body throws an exception. Volatile member functions check volatile class invariants instead. 7.Check the function postconditions (in logic-and with the overridden function postconditions when subcontracting). These are checked only if the body does not throw an exception.
staticが絡む場合
CONTRACT_CLASS( class (Foo) ) { CONTRACT_CLASS_INVARIANT( data >= 0, // 非staticな不変条件 static class( count_ >= 0 // staticな不変条件 ) ) public: CONTRACT_CONSTRUCTOR( public (Data) ( int data ) precondition( data >= 0 ) postcondition( auto old_count = CONTRACT_OLDOF count(), data_ >= 0, count_ == old_count + 1 ) initialize( data_( data ) ) ) { ++count_; } CONTRACT_FUNCTION( public static int (count) () // 事前条件と事後条件が書かれてませんが、書く場合は // staticなものしか触れないことに注意! ) { return count_; } private: int data_; static int count_; }; int Foo::count_ = 0;
クラスのstaticな不変条件を書くには、CONTRACT_CLASS_INVARIANT(_TPL)マクロの中に
static class ( statement1, statement2, ... )
と書きます。
Volatileも似た感じにかけます。説明はなくても分かると思うので下にサンプルコード記して終わりとします。
- Boost.Contractの本家様のコード CONTRACT_CLASS( template( typename T ) class (shared_instance) ) { CONTRACT_CLASS_INVARIANT_TPL( queries() >= 0, // Non-volatile class invariants. volatile class( // Volatile class invariants. object() // ... ) ) CONTRACT_CONSTRUCTOR_TPL( public explicit (shared_instance) ( (T*) the_object ) precondition( the_object ) postcondition( object() == the_object ) initialize( object_(the_object), queries_(0) ) ) {} CONTRACT_DESTRUCTOR_TPL( public virtual (~shared_instance) ( void ) ) { delete object_; } CONTRACT_FUNCTION_TPL( // Contracts for volatile member function. public (T const volatile* const) (object) ( void ) const volatile // No preconditions nor postconditions for this example but when // present object is `const volatile` within assertions. ) { queries_++; return object_; } CONTRACT_FUNCTION_TPL( public int (queries) ( void ) const ) { return queries_; } private: T volatile* object_; private: mutable int queries_; };
Warning!!
そろそろ慣れてきましたかね、ここでも注意ががが
- コンストラクタ及びメンバ関数内での戻り値の型、関数名、引数の型
最初、フリー関数でのCONTRACT_FUNCTIONマクロあたりの説明で、a-Z、0-9までの文字で構成されているなら中括弧が要らないと書きましたね。ここでそれやると死にます。で、エラーもよくわからないのでハマります。CONTRACT_CONSTRUCTORマクロ,メンバ関数でのCONTRACT_FUNCTIONマクロは戻り値の型や関数名、引数の型はint等の組み込み型以外は全部囲ってください。フリー関数でもとりあえず囲っておく習慣つけとけばいいでしょう。中括弧を付けなくてもいいのは、intなどのkeywordsの場合のみです。
- Boost.Contract の Tutorialより引用 The constructor name (ivector) and the non-fundamental parameter type (size_type) must be wrapped within parenthesis (because they are not known keywords and they are not at the end of a syntactic element).
「組み込み型以外の型だ!囲め囲め!!」「(((type)))」
やりすぎです、エラーになるのでほどほど*4にしましょう。
-
継承とSubcontracting
CONTRACT_CLASS( // 重複を許さないvector class (unique_vector) ) { CONTRACT_CLASS_INVARIANT( v_.size() >= 0 ) public: CONTRACT_CONSTRUCTOR( public (unique_vector) () postcondition( true ) ) {} CONTRACT_DESTRUCTOR( public virtual (~unique_vector) () ) {} CONTRACT_FUNCTION( public virtual void (add) ( const int value ) precondition( std::find( std::begin( v_ ), std::end( v_ ), value ) == std::end( v_ ) ) postcondition( auto old_size = CONTRACT_OLDOF v_.size(), auto old_found = CONTRACT_OLDOF std::find( std::begin( v_ ), std::end( v_ ), value ) != std::end( v_ ), old_found ? true : std::find( std::begin( v_ ), std::end( v_ ), value ) != std::end( v_ ), old_found ? true : v_.size() == old_size + 1 ) ) { v_.push_back( value ); } protected: std::vector<int> v_; };
CONTRACT_CLASS( // 重複を許すvector class (duplicate_vector) extends ( public unique_vector ) // 自動的にSubcontractされる ) { CONTRACT_CLASS_INVARIANT( v_.size() >= 1 ) public: CONTRACT_CONSTRUCTOR( public (duplicate_vector) ( const int value ) postcondition( v_.size() == 1 ) ) { v_.push_back( value ); } CONTRACT_DESTRUCTOR( public virtual (~duplicate_vector) () ) {} public: CONTRACT_FUNCTION( public virtual void (add) ( const int value ) precondition( std::find( std::begin( v_ ), std::end( v_ ), value ) != std::end( v_ ) ) postcondition( auto old_size = CONTRACT_OLDOF v_.size(), auto old_found = CONTRACT_OLDOF std::find( std::begin( v_ ), std::end( v_ ), value ) != std::end( v_ ), old_found ? v_.size() == old_size : true ) ) { if( std::find( std::begin( v_ ), std::end( v_ ), value ) == std::end( v_ ) ) { unique_vector::CONTRACT_MEMBER_BODY( add )( value ); } } };
基本構文の解説なのでサンプルコードの実用性の無さは気にしないでください…
ここでは実際継承してます。まず、継承(Subcontractもやってくれる)の書き方ですが、
CONTRACT_CLASS( class (Derived) extends( Base1, // Base1がclassならprivate、structならpublic継承 public Base2, // public継承 public virtual Base3, // public 仮想継承. virtual public Base3だとダメです. ... ) ) { ... }
extendsで継承できます。複数継承する場合はコンマで区切ってください。
同時にextendsできる数がこれまた決まってまして、CONTRACT_CONFIG_INHERITANCE_MAXマクロで決まってます(デフォルトは4です)。
継承した場合、それぞれの契約がどうなるか見て行きましょう。
CONTRACT_CLASS_INVARIANTマクロは、継承元と"and"の関係にあります。基底クラスの不変条件に派生クラスの不変条件が追加された感じですね。継承したからって元の不変条件が無効になることはありません。
preconditionですが、これは継承元と"or"の関係にあたります。基底クラスの precondition が成立しているか、派生クラスの precondition が成立していれば契約は守られたことになります。また、基底または派生側のどちらかが precondition を省略して書かなかった場合、それはすなわち事前条件がない(常に precondition が守られている)とおなじになってしまいます。オーバーロードしてかつ事前条件を変えたくないなら、基底クラスと全く同じ precondition を派生側にも書いてください。
postconditionは、"and"の関係です。CONTRACT_CLASS_INVARIANTマクロと同じです。
ちょっと英語苦手なので一応引用元を載せておきますね…
・p logic-and q is true if and only if both p and q are true but q is evaluated only when p is true. ・p logic-or q is true if and only if either p or q is true but q is evaluated only when p is false. ・Class invariants are checked in logic-and with the class invariants of all base classes following the inheritance order. ・Preconditions are checked in logic-or with the preconditions of all overridden functions following the inheritance order. ・Postconditions are checked in logic-and with the postconditions of all overridden functions following the inheritance order.
今回は単純な仮想関数ですが、純粋仮想関数もかけます。書くなら
CONTRACT_FUNCTION( public virtual void (foo) () ) = 0;
てな感じで書いてください。
あと気になるのが
unique_vector::CONTRACT_MEMBER_BODY( add )( value );
の部分でしょう。これは要するに継承元のaddメソッドを呼び出しているだけなんですが、そのまま呼び出してしますと無限再帰に陥ります(現在のライブラリの仕様)。なので、CONTRACT_MEMBER_BODY( function-name )( param ) という感じで呼び出さないといけません。この謎仕様は将来直される予定です。*5どうなるのか楽しみですね。
ちなみに、CONTRACT_MEMBER_BODYは後ほど違う場面で大事になってきます。
Boost.Contractのマクロを使う必要がないものって?
先ほどデストラクタの説明の最後に少し触れたのでついでに書いておきます。
Boost.Contractを使っていて、契約しなくても問題がないのが以下の3つです。
- フリー関数で、事前条件と事後条件がない場合
- privateまたはprotectedなコンストラクタ、デストラクタ、メンバ関数で、かつ precondition と postcondition がない場合(クラスの不変条件にかかわらず、privateまたはprotectedなメンバは不変条件を決してチェックしないため)
- publicなコンストラクタ、デストラクタ、メンバ関数でもそれらがすべて precondition と postcondition を持たず、かつクラスに不変条件がない場合
これ以外はかけるなら全部書いてね!
templateでの契約
フリー関数
template< typename T > T func( const T& value ) // precondition: value < std::numeric_limits<T>::max() { ... }
こんなコードがあるとしましょう。templateでも契約することができます。いつものように契約した後は下に書いたようになります。
CONTRACT_FUNCTION( template( typename T ) // Template parameter(s). T (func) ( (const T&) value ) precondition( value < std::numeric_limits<T>::max() ) ) { ... }
template< typename T > ではなく、template( typename T ) となっているのがポイントです。それ以外は非templateなものと同じだと考えてもらって大丈夫です。
Tutorialから、引っ張ってきたものを載せます。
CONTRACT_FUNCTION( // Class template parameters. template( typename A // Type template parameter. , int B // Value template parameter. , class C, default A // Optional type template parameter. , (A) D, default B // Optional value template parameter. , template( // Template template parameter: Outer template typename X // uses `()` but rest uses usual syntax. , template< typename S, typename T = S > class Y , class Z = int , Z V = 0 ) class E, default x // Optional template template parameter. ) void (f) () ) { // ... }
template templateパラメータより深いtemplateを書く場合は、いつもの様にtemplate< typename T >と書くようです。
ややこしいすごく明解でわかりやすいですね(震え声)
クラスの場合
CONTRACT_CLASS( template( typename T ) class (Hoge) ) { CONTRACT_CLASS_INVARIANT_TPL( void ) public: CONTRACT_CONSTRUCTOR_TPL( public (Hoge) () ) {} CONTRACT_DESTRUCTOR_TPL( public (~Hoge) () ) {} public: CONTRACT_FUNCTION_TPL( public void (func) () precondition( true ) postcondition( true ) ) {} };
templateの書き方はフリー関数の時と同じですね。
変わったといえば、それぞれのマクロの後ろに _TPL がついたことぐらいでしょうか。templateなクラスではこう書くように決まっています。ほかは今までやってきたことと同じなので大丈夫でしょう。
複雑なtemplateは、さっきフリー関数の最後で引用した明解でわかりやすい例と全く同じように書けますので省略します。