アサート 解答ページ | Programming Place Plus 新C++編

トップページ新C++編アサート

このページの概要

このページは、練習問題の解答例や解説のページです。



解答・解説

問題1 (確認★)

assertマクロを次のように使うことには問題があります。理由を説明して、正しく修正してください。

int n {-2};
assert(++n >= 0);


assertマクロは、NDEBUGマクロが定義されている状況下では、一切何も行わないように置換されます(本編解説)。一般に、開発中は NDEBUGマクロを定義せず、最終的に製品となるバージョンでは定義します(本編解説)。

assertマクロが何も行わないコードに置換されるということは、assert(++n >= 0); という文は ; のような空文になってしまうということです。したがって、変数n がインクリメントされなくなります。これは必要だったはずの処理が消えてしまったということです。開発中は正常に動作していたのに、製品バージョンでは正常な動作ではなくなってしまうでしょう。

#define NDEBUG
#include <cassert>
#include <iostream>

int main()
{
    int n {-2};
    assert(++n >= 0);
    std::cout << n << "\n";
}

実行結果:

-2

実行結果から、インクリメントが行われていないことが分かります。

assertマクロには、開発中でも本番用でも必要なコードを与えてはいけません。この例では、assertマクロの手前でインクリメントを済ませておくようにします。

#define NDEBUG
#include <cassert>
#include <iostream>

int main()
{
    int n {-2};
    ++n;
    assert(n >= 0);
    std::cout << n << "\n";
}

実行結果:

-1

問題2 (確認★)

以下の中から、static_assert で記述できる想定はどれか、すべて選んでください。

  1. short型の大きさが 2バイト以上である
  2. int型と long型の大きさが同じである
  3. 標準入力から入力された整数が 0 でない
  4. constexpr変数 X の値が 1000以上である
  5. NDEBUGマクロが定義されている


static_assert はコンパイル時に判定されるので、定数式しか与えられません(本編解説)。

1番は、static_assert(sizeof(short) >= 2, ""); のように記述できます。

2番は、static_assert(sizeof(int) == sizeof(long), ""); のように記述できます。

3番は記述できません。標準入力から入力を受け取るのは、プログラムを実行してからの話です。static_assert はコンパイル時に判定を行うものなので、記述しようがありません。この用途では assertマクロが使えそうですが、ユーザーがどんな入力を行うかは想定できないことですし、assert ではプログラムの実行自体が異常終了してしまうので、assert を使うのは不適切です。都合の悪い入力は if文で判定し、ユーザーにその入力が受け入れられないものであることを伝えるようにします。

4番は、static_assert(X >= 1000, ""); のように記述できます。constexpr変数は定数式の中で使用できます。

5番は記述できません。マクロが定義されているかどうかを判定する方法は、プリプロセスの中で機能する #ifdef や #if defined といったものだけです(「プリプロセス」のページを参照)。NDEBUGマクロが定義されているときだけ 1 になる constexpr変数を定義するなどして、間接的に調べることはできますが、直接は記述できません。

問題3 (応用★★)

ポーカープログラムの judge_poker_hand関数は、引数で渡されてくるカード情報が、5枚分であることや、適切に整列済みであることを求めています。現状、if文でチェックしていますが、アサートで置き換えてください。

現在のポーカープログラムの最終形は、「プリプロセス」のページの練習問題の解答にあります。


judge_poker_hand関数は次のようになっています。

// 役を判定する
PokerHand judge_poker_hand(const cards_type& cards)
{
    if (cards.size() != hand_card_num) {
        return PokerHand::no_pair;
    }
    if (std::is_sorted(std::cbegin(cards), std::cend(cards), cards_sort_compare) == false) {
        return PokerHand::no_pair;
    }

    // 以下、役の判定コードが続く
}

冒頭の2つの if文が、渡されてきたカードの枚数と、それが整列済みであることを判定しています。いずれも不適切であれば、ただちに return文で終了させていますが、これはこの下に続いている役判定のコードが、カードは5枚で整列済みであることを想定したコードになっているからです。不適切なまま続行させると、何が起こるか分かりません。現状のように「役無し」と返してしまうと、正常に判定が行われた結果の「役無し」と区別がつきませんし、何かしらのエラー値を返すようにすると、judge_poker_hand関数の呼び出し元がチェックして、何かしらの処理を継続するコードを書かなければなりません。

誤って 5枚ではない枚数のカード情報を渡してしまうことも、整列せずに渡してしまうことも、結局はプログラマーが犯したミスですから、開発中に検出されるのが正しいです。こういうことは assert で記述する方が適切といえます。以下のように書き換えられます。なお、#include <cassert> が必要です。

// 役を判定する
PokerHand judge_poker_hand(const cards_type& cards)
{
    assert(cards.size() == hand_card_num);
    assert(std::is_sorted(std::cbegin(cards), std::cend(cards), cards_sort_compare));

    // 以下、役の判定コードが続く
}

問題4 (応用★★★)

assertマクロで停止したときに出力されるメッセージに加えて、任意のメッセージも出力できるようなアサートマクロを作ってください。たとえば次のような使い方ができるようにします。

my_assert(v >= 0, "v must not be negative");


1手ずつ進めていきましょう。

まず、2つの引数をもったマクロなので、関数形式マクロとして定義します。

#define my_assert(cond, msg)  /* ???? */

assertマクロは、条件式を評価した結果が false になるときにだけ、以下の処理を行うのでした(本編解説)。

  1. 以下の内容を標準エラーへ出力する(形式は処理系定義)
    • 条件式を文字列化したもの
    • __FILE__、__LINE__、__func__ の結果
  2. std::abort関数を呼び出して、プログラムを異常終了させる

まず、条件式による分岐を書きます。実引数は () で取り囲むのが安全です(「プリプロセス」のページを参照)。

#define my_assert(cond, msg)  if (!(cond)) { /* ???? */ }

条件式を文字列化したものは、#演算子を使って得られます(「プリプロセス」のページを参照)。出力先は標準エラーなので、std::cerr に出力します。アサートで停止したことが分かるように、最初に “Assertion failed:” と出力することにします。

#define my_assert(cond, msg)  if (!(cond)) { std::cerr << "Assertion failed: " << #cond << "\n"; }

__FILE__、__LINE__、__func__ の結果も出力します。

#define my_assert(cond, msg)  if (!(cond)) { std::cerr << "Assertion failed: " << #cond << ", " << __FILE__ << " " << __LINE__ << " " << __func__ << "\n"; }

長くなってきたので、改行してもいいでしょう。マクロ定義内で改行するには、行末に \ を置きます(「プリプロセス」のページを参照)。

#define my_assert(cond, msg)  \
    if (!(cond)) { \
        std::cerr << "Assertion failed: " << #cond << ", " << __FILE__ << " " << __LINE__ << " " << __func__ << "\n"; \
    }

my_assertマクロの場合は、実引数で指定したメッセージを出力したいので、この出力も追加しましょう。

#define my_assert(cond, msg)  \
    if (!(cond)) { \
        std::cerr << "Assertion failed: " << #cond << ", " << __FILE__ << " " << __LINE__ << " " << __func__ << "\n"; \
        std::cerr << (msg) << "\n"; \
    }

そして、std::abort関数を呼び出して、異常終了させます。

#define my_assert(cond, msg)  \
    if (!(cond)) { \
        std::cerr << "Assertion failed: " << #cond << ", " << __FILE__ << " " << __LINE__ << " " << __func__ << "\n"; \
        std::cerr << (msg) << "\n"; \
        std::abort(); \
    }

最後に、assertマクロと同様、NDEBUGマクロの定義の有無によって、有効・無効を切り替えられるようにしておきます。

#if defined(NDEBUG)
#define my_assert(cond, msg) ((void)0)
#else
#define my_assert(cond, msg)  \
    if (!(cond)) { \
        std::cerr << "Assertion failed: " << #cond << ", " << __FILE__ << " " << __LINE__ << " " << __func__ << "\n"; \
        std::cerr << (msg) << "\n"; \
        std::abort(); \
    }
#endif

これで完成しました。このマクロは、std::cerr や std::abort関数を使っているので、<iostream> や <cstdlib> のインクルードが必要であることに注意してください。my_assertマクロの定義をどこかのヘッダファイルに記述して使おうとするのなら、そのヘッダファイル自身でインクルードするべきです(「ヘッダファイル」のページを参照)。

// my_assert.h

#ifndef MY_ASSERT_H_INCLUDED
#define MY_ASSERT_H_INCLUDED

#include <cstdlib>
#include <iostream>

// アサートマクロ
// cond: 条件式
// msg: cond が false になったときに標準エラーに出力するメッセージ
//
// 標準の assertマクロに、任意のメッセージを出力できるようにしたもの。
// NDEBUGマクロが定義されているときは、何も行わない。
#if defined(NDEBUG)
#define my_assert(cond, msg) ((void)0)
#else
#define my_assert(cond, msg)  \
    if (!(cond)) { \
        std::cerr << "Assertion failed: " << #cond << ", " << __FILE__ << " " << __LINE__ << " " << __func__ << "\n"; \
        std::cerr << (msg) << "\n"; \
        std::abort(); \
    }
#endif

#endif

簡単に動作確認してみましょう。

#include "my_assert.h"

int main()
{
    int v {-1};

    my_assert(v >= 0, "v must not be negative");
}

実行結果:

Assertion failed: v >= 0, c:\test_program\main.cpp 7 main
v must not be negative
(強制終了)


参考リンク



更新履歴




はてなブックマーク に保存 Pocket に保存 Facebook でシェア
X で ポストフォロー LINE で送る noteで書く
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー
先頭へ戻る