LoginSignup
0
0

More than 1 year has passed since last update.

C++で、実行中に作成した文字列をassertで出力する方法

Last updated at Posted at 2021-06-19

※本記事では、「ファイルを読み込み、成否をbool型で返すLoadFile関数」があると仮定して解説する。

assertの概要

(Visual)C++のassertマクロは、エラー箇所で自動的に実行を停止することができる、便利なデバッグ用機能だ。

assert関数のもっともシンプルな利用例
// ※LoadFile関数は、ファイルを読み込み、成否をbool型で返す関数とする
bool loadResult = LoadFile("config.txt");
// assertマクロ関数は、引数がtrueだったときのみ動作する
assert(loadResult);

//---------------------------------------
// assert出力結果抜粋:LoadFile(loadResult)

上記のソースではエラーメッセージとして分かりづらい。
そこで、assertの動作条件がtrueかfalseかであることを利用して、このような使い方もできる。

assertで任意の文字列を表示する
// ※LoadFile関数は、ファイルを読み込み、成否をbool型で返す関数とする
if (!LoadFile("config.txt"))
{
    // assertマクロ関数は、引数がtrueだったときのみ動作する
    assert(0 && "設定ファイル読み込み失敗");
}

//---------------------------------------
// assert出力結果抜粋:0 && "設定ファイル読み込み失敗"

実行中に作成した文字列をassertできない

さて、ここで本題である。
今回の例の場合、「読み込みに失敗したファイル名」をassert画面で表示したいときがあるだろう。
とりあえず文字列を渡しておけば良さそうに見えるので、思った通りに書いてみる。

うまくいかない例
// ※LoadFile関数は、ファイルを読み込み、成否をbool型で返す関数とする
std::string filename = "config.txt";
if (!LoadFile(filename))
{
    std::string errorMsg = "読み込み失敗:" + filename;
    assert(0 && errorMsg.c_str());
}

//---------------------------------------
// assert出力結果抜粋:0 && errorMsg.c_str()

errorMsg.c_str()の戻り値が展開されず、そのままソースコードの内容が展開されてしまった。

とりあえず解決方法

先にちゃんと動いてくれる例を提示しておく。
assertではなく、_wassertを使うことになる。
結論としては、使いやすい自作assert作るしかないんですね……

実行中に作成した文字列をassertで出力する
// ※LoadFile関数は、ファイルを読み込み、成否をbool型で返す関数とする
std::string filename = "config.txt";
if (!LoadFile(filename))
{
    // エラーメッセージ
    std::string errorMsg = "読み込み失敗:" + filename;

    //ワイド文字列に変換
    WCHAR* _wtext = new WCHAR[strlen(errorMsg.c_str()) + 1];
    mbstowcs_s(nullptr, _wtext, strlen(errorMsg.c_str()) + 1, errorMsg.c_str(), _TRUNCATE);

    // _wassertを使って、文字列出力
    // (現在のファイルや行数は__FILE__マクロと__LINE__マクロで取得可能)
    _wassert(_wtext, _CRT_WIDE(__FILE__), (unsigned)(__LINE__));

    // ワイド文字列を解放
    delete[] _wtext;
}

//---------------------------------------
// assert出力結果抜粋:読み込み失敗:config.txt

超絶簡易な解説

以下、上記のソースの簡易な解説。

assertマクロの中身

まずは、assertマクロが何故このような挙動をするのか、中身を見て確認してみよう。

assertマクロのソース
#define assert(expression) (void)(                                                       \
        (!!(expression)) ||                                                              \
        (_wassert(_CRT_WIDE(#expression), _CRT_WIDE(__FILE__), (unsigned)(__LINE__)), 0) \
    )

ここで着目したい点は、以下の4点。

  • 本質的には「_wassert」関数を使ってassertを出力していること。
  • _wassert関数の各引数の文字列は、「_CRT_WIDE」マクロ関数でワイド文字列に変換してから渡していること。
  • assertマクロの引数expressionが、「#」でソースコード上の記述そのままで出すようになっていること。
  • 該当する箇所のファイル名と行数を、マクロ「__FILE__」「__LINE__」で取得していること。

これを踏まえると、「自力で_wassert関数を呼び出す。ただし、文字列はワイド文字列に変換する必要がある。」ということだ。

ただし、ワイド文字列に変換する「_CRT_WIDE」マクロ関数は、本質的には「文字列リテラルをの先頭に「L」を追加しているだけ」である。
つまり、「_CRT_WIDE(errorMsg.c_str())」 のようにシンボル名を入れてしまうと「LerrorMsg.c_str()」としてコンパイルされることになり、コンパイルエラーが発生する。
すなわち、自分でワイド文字列に変換する処理を書いてあげる必要がある
今回はmbstowcs_s関数を使用した。

結論

つまり、自作assert作れってことですね、ハイ。

0
0
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0