【C++ アイデア】戻り値をチェックするアサート
アサートの罠
アサートは便利なものですが、油断するとこんなコードを書いてしまうかもしれません。
#include <stdio.h> #include <assert.h> class A { int m_Value; public: A() : m_Value( 0 ){} // 値をセットします。戻り値はセットする前の値です。 int setValue( int value ) { int ret = m_Value; m_Value = value; return ret; } }; int main() { A a; assert( a.setValue( 10 ) == 0 ); return 0; }
このコードはリリースビルド時はa.setValue( 10 )
が実行されなくなってしまいます。assertはリリースビルド時(NDEBUG定義時)に無効になるからです。このassertの性質を知っていても意外とやってしまうミスかもしれません。以下のように、assertの外で戻り値を受け取っておく必要があります。
int main() { A a; int result = a.setValue( 10 ); assert( result == 0 ); return 0; }
しかしこれもまだ少し問題があります。リリースビルドだと変数resultは未使用だと警告が出るかもしれないからです。なので以下のように修正します。
int main() { A a; int result = a.setValue( 10 ); (void)result; // 未使用警告対策 assert( result == 0 ); return 0; }
なんか面倒くさくないですか?そこで今回は戻り値をチェックするためのアサートのアイデアを紹介したいと思います。
戻り値をチェックするアサート
まずは「戻り値をチェックするアサート」の使い方から。
int main() { A a; RESULT_ASSERT( 0 ) a.setValue( 10 ); return 0; }
このRESULT_ASSERTマクロは「自身の引数」と「右側の式の評価結果」が同じ値であることを表明(assert)するアサートです。上の例ではa.setValue( 10 )
の結果が0
であることを表明しています。a.setValue( 10 )
の結果が0
でなかった場合はプログラムを停止させます。RESULT_ASSERTマクロはリリースビルド時は無効になるので、RESULT_ASSERT( 0 ) a.setValue( 10 );
はリリースビルド時にはa.setValue( 10 );
になります。
通常のassertと比較してみましょう。
// 通常のassert int result = a.setValue( 10 ); (void)result; // 未使用警告対策 assert( result == 0 ); // RESULT_ASSERT RESULT_ASSERT( 0 ) a.setValue( 10 );
とてもすっきりしました。
戻り値をチェックするアサートの実装
「戻り値をチェックするアサート」の実装は以下のようになっています。
#ifdef NDEBUG #define RESULT_ASSERT( expectResult ) #else #define RESULT_ASSERT( expectResult ) GetResultAssertHelper( expectResult ) = template< class T > class ResultAssertHelper { public: ResultAssertHelper( const T& expectResult ) : m_ExpectResult( expectResult ){} ResultAssertHelper& operator=(const T& result ) { assert( m_ExpectResult == result ); return *this; } const T& m_ExpectResult; }; template< class T > ResultAssertHelper<T> GetResultAssertHelper( const T& expectResult ){ return ResultAssertHelper<T>( expectResult ); } #endif
仕組みは単純で、operator=
で右側の評価結果を得るようにして、それを使ってassertを行っているだけです。ちなみにoperator=
である意味は特にありません。2項演算子ならなんでもいいと思います。このResultAssertHelperの実装ではアサートによる停止時にソースコードの位置などが正しく表示されないので、そこは工夫した方がいいかもしれません。