C++ Template Metaprogramming の Exercise 4-2
Variadic Templates Verで書いた。
struct error {}; template< bool B, typename ... Args > struct logical_and_; template< typename head, typename ... tail > struct logical_and_<false, head, tail...> { typedef mpl::false_ type; }; template< typename head, typename ... tail > struct logical_and_<true, head, tail...> { typedef typename logical_and_<head::type::value, tail...>::type type; }; template< typename T > struct logical_and_<true, T> { typedef typename T::type type; }; template< typename head, typename ... tail > struct logical_and { static const bool value = head::type::value; typedef typename logical_and_<value, tail...>::type type; }; template< bool B, typename ... Args > struct logical_or_; template< typename head, typename ... tail > struct logical_or_<false, head, tail...> { typedef typename logical_or_<head::type::value, tail...>::type type; }; template< typename T > struct logical_or_<false, T> { typedef typename T::type type; }; template< typename head, typename ... tail > struct logical_or_<true, head, tail...> { typedef mpl::true_ type; }; template< typename head, typename ... tail > struct logical_or { static const bool value = head::type::value; typedef typename logical_or_<value, tail...>::type type; }; int main() { BOOST_STATIC_ASSERT(( logical_or<mpl::true_, error>::type::value )); BOOST_STATIC_ASSERT(( !logical_and<mpl::false_, error>::type::value )); BOOST_STATIC_ASSERT(( logical_or<mpl::false_, mpl::false_, mpl::true_, error>::type::value )); BOOST_STATIC_ASSERT(( !logical_and<mpl::true_, mpl::true_, mpl::false_, error>::type::value )); }
まだ慣れてない。
mpl::equalに渡すところをstd::is_sameにしてしまった
ちょっとハマったのでメモ。
ある日、以下の様なコードを書いてました。
typedef mpl::vector_c<int, 1, 2, 3> foo; typedef mpl::transform<foo, mpl::plus<_1, mpl::int_<1>>>::type bar; typedef mpl::vector_c<int, 2, 3, 4> expected; int main() { BOOST_STATIC_ASSERT(( std::is_same<bar, expected>::value )); }
てっきり mpl::transform で mpl::vector_c が返ってくると期待していた私は、BOOST_STATIC_ASSERTで失敗しているのに気がついてかなりびっくりしました。
ここでの mpl::transform によって返ってくる型は
struct mpl::vector3< struct mpl::integral_c<int, 2>, struct mpl::integral_c<int, 3>, struct mpl::integral_c<int, 4> >
ぜんぜん違うじゃねーか
なので、多分 std::is_same と相性そんなに良くないのかもしれません。
素直に mpl::equal 使いましょう。
BOOST_STATIC_ASSERT(( mpl::equal<bar, expected>::value ));
xrandr で 1920x1080 が出てくれない時の対処法
http://samuelmartin.wordpress.com/2012/05/29/enabling-resolutions-in-ubuntu-12-04-lubuntu-12-04/
ここに書いてあるとおり、まずターミナルで
gtf 1920 1080 60
と入力すると、
# 1920×1080 @ 60.00 Hz (GTF) hsync: 67.08 kHz; pclk: 172.80 MHz Modeline "1920x1080_60.00" 172.80 1920 2040 2248 2576 1080 1081 1084 1118 -HSync +Vsync
のように出力されると思います。
その後、ターミナルで
xrandr
と入力し、接続されているモニタを確認します。
Screen 0: minimum 1 x 1, current 1920 x 1080, maximum 8192 x 8192 Virtual1 connected 1920×1080+0+0 0mm x 0mm
上記のように出力されているなら、次に進みましょう。
その後、 /usr/share/X11/xorg.conf.d/10-monitor.conf を作成し、以下のように編集します。
Section "Monitor" Identifier "Monitor0" Modeline "1920x1080_60.00" 82.97 1000 1064 1168 1336 1000 1001 1004 1035 -HSync +Vsync EndSection Section "Screen" Identifier "Screen0" Device "Virtual1" Monitor "Monitor0" DefaultDepth 24 SubSection "Display" Depth 24 Modes "1920x1080_60.00" "1024x768" EndSubSection EndSection
Modeline は xrandr で出力された "1920x1080_60.00" 172.80 1920 2040 2248 2576 1080 1081 1084 1118 -HSync +Vsync の部分、Device は Virtual1 の部分、 Modes は "1920x1080_60.00" の部分です。
その他は書いてある通りで構いません。
その後、保存してrebootすれば適用されてるはずです。
Boost.Contract の出力先を変更する
あけましておめでとうございます。今年もよろしくお願いします。
さて、新年一発目の記事はタイトルにあるとおりです。
そのコードが以下になります。
//main.cc #include <contract.hpp> #include <boost/exception/all.hpp> #include <iostream> #include <fstream> class my_exception : public boost::exception, public std::exception {}; CONTRACT_FUNCTION( void (func) ( const int arg ) precondition( arg >= 0 ? true : throw my_exception() ) // 違反なら例外を投げる ) { // ... } void throwing_handler( const contract::from& context ) { // ここでは出力先をファイルにしています const std::string log_file_path( "./contract_log.txt" ); std::ofstream ofs( log_file_path ); try { // 投げられている例外(裏で溜まってる?)をcatchさせる throw; } catch(contract::broken& failure) // contract の assertion で // false になればこれが投げられる { ofs << failure.file_name() << "(" << failure.line_number() << "): contract assertion \"" << failure.assertion_code() << "\" failed " << std::endl; } catch(std::exception& failure) // ユーザーがstd::exceptionを継承して作った例外 // (example. my_exception)とか、 // 標準の例外(example. std::bad_alloc) // が投げられたらこっち { ofs << "contract failed: " << failure.what() << std::endl; } catch(...) // その他 { ofs << "contract failed with unknown error" << std::endl; } // デストラクタからの例外は投げない! // すなわち、std::terminateとはおさらばさ! if( context == contract::FROM_DESTRUCTOR ) { ofs << "exception from destructor!"; } else { // re-throwすることで、先ほどのアクティブな例外を伝播させる throw; } } int main() { // precondition と postcondition でのアサーションを // 捉える関数をthrowing_handlerに設定する // ほかにも、contract::set_class_invariant_broken, // contract::set_block_invariant_broken // 等がある contract::set_precondition_broken( &throwing_handler ); contract::set_postcondition_broken( &throwing_handler ); try { func( -1.0 ); } catch( const std::exception& e ) // throwing_handlerの最後で // re-throwされたのがここでcatchされる { // こっちは標準エラー出力 std::cerr << e.what() << std::endl; } }
ちなみに、contract::brokenはstd::logic_errorを継承していて、std::exceptionで捉えることが可能です。
標準のthrowing_handlerにあたるdefault_broken_handlerという関数は、デストラクタから例外が投げられたら常にstd::terminateを呼び出してます。ユーザーがset_precondition_broken等を呼びだし、上記のようなハンドリングをすることでそれを防ぐこともできます。
余談ですが、僕はこんな感じで書いてます。
void throwing_handler( const contract::from& context ) { if( context == contract::FROM_DESTRUCTOR ) { //... } else { throw; } } int WINAPI _tWinMain( HINSTANCE, HINSTANCE, LPTSTR, int ) { try { contract::set_precondition_broken( &throwing_handler ); contract::set_postcondition_broken( &throwing_handler ); //... } catch( const boost::exception& e ) { //... } catch( const std::exception& e ) { //... } catch( ... ) { //... } return 0; }
throwing_handlerがえらくすっきりしていますが、Main関数のほうで大きくtryで囲ってるので、ちゃんと例外が拾えます。
この書き方だと、contract以外でMainまで漏れてきた例外を全部catchできるので、結構好きなんですが、どうなんでしょうか。
英語の本が読みたいから英語を勉強したいと思っているあなたに送る言葉
とりあえずその読みたい英語の本を買って読みましょう。
Boost.Contract その2 - C++ Advent Calender 2012
その1の続きです。
宣言と実装の分離
基本構文はだいたい押さえましたね。しかし、実際に使うとなると大事なことを忘れています。
このまま行くと「あれ?そういえば宣言と実装分けられないのかな?」となるのは確実です。
Boost.Contractではそれが可能です。自分で書くと結構たいへんだったのでTutorialのを引用しちゃいました。
// body_natural.hpp // Copyright (C) 2008-2012 Lorenzo Caminiti // Distributed under the Boost Software License, Version 1.0 // (see accompanying file LICENSE_1_0.txt or a copy at // http://www.boost.org/LICENSE_1_0.txt) // Home at http://sourceforge.net/projects/contractpp #ifndef BODY_NATURAL_HPP_ #define BODY_NATURAL_HPP_ #include <contract.hpp> //[body_natural template< typename T, T Default = T() > // Class forward declaration. class natural ; template< typename T > // Function forward declaration. bool less ( natural<T> const& left, natural<T> const& right ) ; CONTRACT_FUNCTION( template( typename T ) bool (greater) ( (natural<T> const&) left, (natural<T> const&) right ) postcondition( auto result = return, result ? not less(left, right) : true ) ) ; // Deferred free function body definition, use `;`. CONTRACT_CLASS( // Class declaration (with contracts). template( typename T, (T) Default ) class (natural) ) { CONTRACT_CLASS_INVARIANT_TPL( get() >= 0 ) CONTRACT_CONSTRUCTOR_TPL( public explicit (natural) ( (T const&) value, default Default ) precondition( value >= 0 ) postcondition( get() == value ) // Unfortunately, no member initializers when body deferred. ) ; // Deferred constructor body definition, use `;`. CONTRACT_DESTRUCTOR_TPL( public (~natural) ( void ) ) ; // Deferred destructor body definition, use `;`. CONTRACT_FUNCTION_TPL( public bool (equal) ( (natural const&) right ) const postcondition( auto result = return, result == not less(*this, right) && not greater(*this, right) ) ) ; // Deferred member function body definition, use `;`. CONTRACT_FUNCTION_TPL( public (T) (get) ( void ) const ) { return value_; } private: T value_; }; CONTRACT_FUNCTION( // Function declaration (with contracts). template( typename T ) bool (less) ( (natural<T> const&) left, (natural<T> const&) right ) postcondition( auto result = return, result ? not greater(left, right) : true ) ) { return left.get() < right.get(); } //] #include "body_natural_impl.hpp" #endif // #include guard
// body_natural_impl.hpp // Copyright (C) 2008-2012 Lorenzo Caminiti // Distributed under the Boost Software License, Version 1.0 // (see accompanying file LICENSE_1_0.txt or a copy at // http://www.boost.org/LICENSE_1_0.txt) // Home at http://sourceforge.net/projects/contractpp #include "body_natural.hpp" //[body_natural_impl // Deferred body definitions, separated from their declarations and contracts. template< typename T > bool CONTRACT_FREE_BODY(greater) ( natural<T> const& left, natural<T> const& right ) { return left.get() > right.get(); } template< typename T, T Default > CONTRACT_CONSTRUCTOR_BODY((natural<T, Default>), natural) ( T const& value ) { value_ = value; } template< typename T, T Default > CONTRACT_DESTRUCTOR_BODY((natural<T, Default>), ~natural) ( void ) { // Do nothing. } template< typename T, T Default > bool natural<T, Default>::CONTRACT_MEMBER_BODY(equal) ( natural const& right ) const { return not less(*this, right) && not greater(*this, right); } //]
// main.cpp // Copyright (C) 2008-2012 Lorenzo Caminiti // Distributed under the Boost Software License, Version 1.0 // (see accompanying file LICENSE_1_0.txt or a copy at // http://www.boost.org/LICENSE_1_0.txt) // Home at http://sourceforge.net/projects/contractpp #include <boost/detail/lightweight_test.hpp> #include "body_natural.hpp" int main ( void ) { natural<int> n, m(123); BOOST_TEST(n.equal(natural<int>())); BOOST_TEST(less(n, m)); BOOST_TEST(greater(m, n)); return boost::report_errors(); }
あれ?結構難しい…?そう思ったら負けです。踏ん張りましょう。
templateの部分がちょっとややこしいかもしれませんが、普段のC++プログラミングと同じなので省略します。
順番に解説していきますね。
フリー関数の分離
CONTRACT_FUNCTION( template( typename T ) bool (greater) ( (natural<T> const&) left, (natural<T> const&) right ) postcondition( auto result = return, result ? not less(left, right) : true ) ) ; // Deferred free function body definition, use `;`.
template< typename T > bool CONTRACT_FREE_BODY(greater) ( natural<T> const& left, natural<T> const& right ) { return left.get() > right.get(); }
いつもなら宣言と同時に中身も書いていましたが、CONTRACT_FREE_BODYマクロを使うことで分離が可能になっています。
postconditionのところに not がありますが、これは「!」と同じだと考えれば分かると思います。!つかってもいいです。
実装側ではtemplateにマクロがかかってませんね。分離するときはマクロで包みません。(代わりに関数名が包まれてます)
ほかは大丈夫でしょう。
コンストラクタ、デストラクタの分離
CONTRACT_CLASS( // Class declaration (with contracts). template( typename T, (T) Default ) class (natural) ) { ... CONTRACT_CONSTRUCTOR_TPL( public explicit (natural) ( (T const&) value, default Default ) precondition( value >= 0 ) postcondition( get() == value ) // Unfortunately, no member initializers when body deferred. ) ; // Deferred constructor body definition, use `;`. CONTRACT_DESTRUCTOR_TPL( public (~natural) ( void ) ) ; // Deferred destructor body definition, use `;`. ... private: T value_; };
template< typename T, T Default > CONTRACT_CONSTRUCTOR_BODY((natural<T, Default>), natural) ( T const& value ) { value_ = value; } template< typename T, T Default > CONTRACT_DESTRUCTOR_BODY((natural<T, Default>), ~natural) ( void ) { // Do nothing. }
CONTRACT_CONSTRUCTOR_BODYマクロ、CONTRACT_DESTRUCTOR_BODYマクロがちょっと特殊でキモいですね。
ちなみに、それぞれ下のような構文になっています。
CONTRACT_CONSTRUCTOR_BODY(class_type, constructor_name) CONTRACT_DESTRUCTOR_BODY(class_type, destructor_name)
templateなクラスですが、CONTRACT_CONSTRUCTOR_BODY_TPLはないので気をつけましょう。
class_typeがtemplateであれば、それも一緒に記述します。記述の仕方が特殊でこれまたきもい。慣れるしかないです。間違えないように注意してください。
あ、class_typeは中括弧で囲んでくださいね!
これでctor,dtorの分離もできましたね。
Warning!!
コンストラクタの分離で落とし穴があります。ソースのコメントにも書いてありましたが、初期化子リスト(initialize( ... ))を使う場合、分離ができません。とても悲しいですが、今のところ対処法を知らないのでどなたか知っていれば教えてください。
メンバ関数の分離
CONTRACT_CLASS( // Class declaration (with contracts). template( typename T, (T) Default ) class (natural) ) { public: ... CONTRACT_FUNCTION_TPL( public bool (equal) ( (natural const&) right ) const postcondition( auto result = return, result == not less(*this, right) && not greater(*this, right) ) ) ; // Deferred member function body definition, use `;`. CONTRACT_FUNCTION_TPL( public (T) (get) ( void ) const ) { return value_; } private: T value_; };
template< typename T, T Default > bool natural<T, Default>::CONTRACT_MEMBER_BODY(equal) ( natural const& right ) const { return not less(*this, right) && not greater(*this, right); }
CONTRACT_MEMBER_BODYマクロを用いて実現します。
CONTRACT_MEMBER_BODY(function_name)
書き方に関してはこう決まってるから、としか言いようがなく、残念です。書き方に慣れてください。
一つ一つ見ていけば大したことはなかったですね。
演算子オーバーロード
演算子オーバーロードもできます。
CONTRACT_CLASS( class (Data) ) { CONTRACT_CLASS_INVARIANT( data_ >= 0 ) public: CONTRACT_CONSTRUCTOR( public (Data) ( const int data ) precondition( data <= 0 ) postcondition( data <= 0 ) initialize( data_( data ) ) ) { } CONTRACT_FUNCTION( public bool operator(==)(equal) ( (const Data&) right ) const ) { return data_ == right.data_; } public: int data_; };
演算子オーバーロードはCONTRACT_FUNCTIONマクロ、(分離するならCONTRACT_FREE_FUNCTION, CONTRACT_MEMBER_FUNCTION)を使います。演算子オーバーロードは他とチョット書き方が違います。
operator(symbol)(arbitrary-alphanumeric-name)
arbitrary-alphanumeric-nameの部分に任意の英数字を入れます。ここでは==だったのでequalと名づけましたが、好きにしていいんじゃないでしょうか。
例外処理
例外処理は普段通り使えますよー。※だだしコンストラクタの初期化子以外に限る
例外が投げられた後のpostcondition等の扱いはその1を参照ください。
CONTRACT_CLASS( class (Data) ) { CONTRACT_CLASS_INVARIANT( data_ >= 0 ) public: ... CONTRACT_FUNCTION( public void (func) () ) { throw std::runtime_error( "Hoge" ); } public: int data_; }; int main() { Data data( 10 ); try { data.func(); } catch( const std::exception& e ) { std::cout << e.what() << std::endl; } }
コンストラクタの初期化子は特別な書き方をします。下のコードを見てください。
CONTRACT_CLASS( template( typename T ) class (array) ) { CONTRACT_CLASS_INVARIANT_TPL( size() >= 0 ) public: struct out_of_memory {}; public: struct error {}; CONTRACT_CONSTRUCTOR_TPL( public explicit (array) ( size_t count ) precondition( count >= 0 ) postcondition( size() == count ) try initialize( data_(new T[count]), size_(count) ) catch(std::bad_alloc&) ( throw out_of_memory(); ) catch(...) ( throw error(); ) ) {} CONTRACT_DESTRUCTOR_TPL( public virtual (~array) ( void ) throw( ) // 例外投げない ) { delete[] data_; } // ...
initializeの部分が try で囲まれていますね。初期化子リストで例外が投げられると、その下のcatchブロックに投げられて、対応したところが呼ばれます。catch( ... ) と書くこともできますよ。
あっ、もういちいちWarningで赤く出さないですが、catchかける最大数も決まってるので…
CONTRACT_LIMIT_CONSTRUCTOR_TRY_BLOCK_CATCHESマクロをご確認ください(長い)。デフォルトは10です。
普段メンバ関数にthrow()とつけてる方は、上記のようにCONTRACT_DESTRUCTOR_TPLマクロの中でやってることをやってください。
Static Assertions
static_assertも使えます!
CONTRACT_FUNCTION( template( typename To, typename From ) (To*) (memcopy) ( (To*) to, (From*) from ) precondition( static_assert(sizeof(To) >= sizeof(From), "destination too small"), static_assert((boost::is_convertible<From, To>::value), "incompatible types"), to, // ぬるぽちぇっく from // ぬるぽちぇっく ) ) ;
static_assertはpreconditionに書こうがpostconditionに書こうがコンパイル時にチェックされます。
Constant Assertions
int foo = 0; CONTRACT_FUNCTION( int (func) ( int x ) ... postcondition( auto result = return, result = 10, // エラー!resultは自動的にconstになる foo = 10, // エラー…にならなーい! ) ) { ... }
グローバル変数 foo が存在しています。よく見てください、postconditionで代入を行なっていますね。
resultはライブラリが自動的にconstにしてくれます(他にもメンバ変数、引数、戻り値、事前条件又は事後条件で宣言した変数etc)。
しかし、fooは代入できてしまって、実際これ呼び出した後fooの値を確認すると10になってます。副作用を伴っててだめですね。
なので、少しでも事故を少なくするために構文が用意されてます。
int foo = 0; CONTRACT_FUNCTION( int (func) ( int x ) ... postcondition( auto result = return, const( foo ) foo = 10 // エラー! ) ) { ... }
const( variable1, variable2, ... )
書き方はこんな感じです。多分殆ど使わないと思います。(だってたいていは自動的にconstですからね
Select Assertions
その1でこんなの見かけませんでした?
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 ); }
old_found ? true : std::find( std::begin( v_ ), std::end( v_ ), value ) != std::end( v_ ), old_found ? true : v_.size() == old_size + 1
この部分、もっと複雑になったら二項演算子じゃ厳しいですよね?じゃあどうするのか。
if文を使います!!(え
CONTRACT_FUNCTION( public virtual void (add) ( const int value ) ... 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_ ), if( old_found ) ( true ) else ( std::find( std::begin( v_ ), std::end( v_ ), value ) != std::end( v_ ) ), if( old_found ) ( true ) else ( v_.size() == old_size + 1 ) ) ) { v_.push_back( value ); }
ifの構文は
if(boolean-condition) ( // Round parenthesis `()`. ... // Comma-separated assertions. ) else ( // Else optional as usual. ... // Comma-separated assertions. )
です。なかでどんな処理してんだこれ…
Conceptとの併用
Conceptとも相性バツグンです。
concept_checkを使う
#include <boost/concept_check.hpp> CONTRACT_CLASS( template( typename T ) requires( boost::CopyConstructible<T> ) class (vector) ) { CONTRACT_CLASS_INVARIANT_TPL( size() <= capacity(), capacity() <= max_size() ) { ... } ...
基本構文を下に書きました。
template( template-parameter1, template-parameter2, ... ) requires( concept1, concept2, ... )
ここでは基本的なconceptであるboostのCopyConstructibleを使っています。もしTがCopyConstructibleでない場合、concept_checkによってコンパイルエラーになります。
将来的な話
将来的には下のようにContractのコンセプトがかけるように考えられているそうな。
C++1yにはconceptはおそらく入ると思いますし、楽しみですね!!
CONTRACT_CONCEPT( auto concept (LessThanComparable) ( typename T ) ( bool operator(<)(less) ( T , T ) ) ) CONTRACT_CONCEPT( concept (InputIterator) ( typename Iterator, typename Value ) ( requires Regular<Iterator>, (Value) operator(*)(deref) ( Iterator const& ), (Iterator&) operator(++)(preinc) ( Iterator& ), (Iterator) operator(++)(postinc) ( Iterator&, int ) ) ) CONTRACT_CONCEPT( // Derived concepts. concept (ForwardIterator) ( typename Iterator, typename Value ) extends( InputIterator<Iterator, Value> ) ( // Other requirements here... ) ) CONTRACT_CONCEPT( // Concept maps can be templated. template( typename T ) concept_map (InputIterator) ( T* ) ( typedef (T&) value_type, typedef (T&) reference, typedef (T*) pointer, typedef ptrdiff_t difference_type ) ) CONTRACT_CONCEPT( concept (Semigroup) ( typename Op, typename T ) extends( CopyConstructible<T> ) ( (T) operator(())(call) ( Op, T, T ), axiom (Associativity) ( (Op) op, (T) x, (T) y, (T) z ) ( op(x, op(y, z)) == op(op(x, y), z); ) ) )
明解でわかりやすい
さすがにこれはキモい
実際開発で使うとき
Debugモードでならまだしも、実際リリースするときにこのままだといくらなんでも遅いです。ありえません。考えたほうがいいです。
そんな時、一発で無効にできるようにマクロが用意されています。
CONTRACT_CONFIG_NO_PRECONDITIONS CONTRACT_CONFIG_NO_POSTCONDITIONS CONTRACT_CONFIG_NO_CLASS_INVARIANTS CONTRACT_CONFIG_NO_BLOCK_INVARIANTS CONTRACT_CONFIG_NO_LOOP_VARIANTS
#defineで宣言すれば、それぞれ対応したものを無効に出来ます。コンパイルするときにオプションで付けてやれば簡単です。
TDDとContractの関係
TDDはオブジェクトの表明を保証するものではないです。あくまで開発手法。
Contractは、本当に仕様って感じだと思ってます。
だから、TDDをやるとしても契約プログラミングを意識することで、より強力なインターフェースをつくり上げることが出来ると考えてます。逆もまた然り。契約プログラミングを行うことで、必要なテスト、テストの記述漏れ防止etcという効果も期待できそうです。
とある人と少し議論になった時に出てきたのが、「テストで書くべきコト⊇契約」という構図です。
難しいですね…どうなんでしょうか。僕より詳しい方、ぜひコメントで教えてください。よろしくお願いします。
とりあえず
結論:両方やれ
まとめ
2日で詰め込んだらこんなことになってしまいました。あんまりまとまってなくて申し訳ない。
本当はもっと書きたかったんですが、記事書き始めが1日だったのでこれぐらいが限界でした。
ご要望があればどんどん追記していくつもりなので、コメントいただければ幸いです。
英語の翻訳も微妙なところあるかもしれないので、間違いに気づいた方いればそれもコメントにお願いします。
Boost.Contractについてですが、なかなかおもしろいんじゃないかなと個人的には思ってます。今度実際の開発で試しに使ってみます。テストと組み合わせればより有効な手法だと思いますね。契約プログラミングかテストかどっちすればいいですか、って聞かれたら、可能な範囲でどっちもすればいいんじゃ?と答えると思います。コードはちょっときもいですが…w
ケースバイケースですよね。Contractは複雑な契約はちょっと苦手ですし。そうなると防御的プログラミングとかも検討することになるとおもいます。
辛かったこと書きます。
実際開発で使ってるのがVSなので、VS2012でコード打ってたんですが、インテリセンスが死ぬわ死ぬわで大変でした。全く意味なかったし…(途中から追い切れなくなって赤い波線が出る始末。もちろんコンパイルは通る)。
それが辛かったことですかね。
勉強になりました。おつかれさまです。
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は、さっきフリー関数の最後で引用した明解でわかりやすい例と全く同じように書けますので省略します。