読者です 読者をやめる 読者になる 読者になる

Flat Leon Works

アプリやゲームを作ってます。

【C++ アイデア】戻り値をチェックするアサート

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の実装ではアサートによる停止時にソースコードの位置などが正しく表示されないので、そこは工夫した方がいいかもしれません。