C++ にてデバッグを出力したいときは、対象コードのファイル名と行番号を出力すると便利です。
そのやり方をまとめます。
- define マクロで
__FILE__
,__LINE__
を出力する std::source_location
で関数呼び出し元の情報を取得する(C++20 以降)
define マクロで __FILE__, __LINE__ を出力する
昔ながらの伝統的なやり方です。
__FILE__
, __LINE__
のマクロはプリプロセッサによって、ファイル名と行番号に置き換わります。
define の中でこれを定義することにより、埋め込み先のファイル名と行番号に置き換えることができます。
define は template に置き換えるべき!、とよく言われますが、define が依然として有用であるケースの一例です。
#include <iostream>
#define DEBUG(expr) \
{ \
std::cerr \
<< "[" << __FILE__ << ":" << __LINE__ << "] " \
<< #expr << " = " << (expr) \
<< std::endl; \
}
int main()
{
DEBUG(1 + 2 + 3);
return 0;
}
[debug_legacy.cc:13] 1 + 2 + 3 = 6
改良版
少し改良して、値をそのまま return させるようにすると、式中にもマクロを埋め込めることができるようになり利便性が上がります。
Rust の dbg!
マクロのようなイメージです。
#include <iostream>
template <typename T>
T _debug(const std::string &filename, size_t lineno, const std::string &expr, T value)
{
std::cerr
<< "[" << filename << ":" << lineno << "] "
<< expr << " = " << value
<< std::endl;
return value;
}
#define DEBUG(expr) _debug(__FILE__, __LINE__, #expr, (expr))
int main()
{
int sum = DEBUG(1 + 2 + 3);
int doubled = DEBUG(2 * sum);
DEBUG(doubled);
return 0;
}
[debug_legacy2.cc:17] 1 + 2 + 3 = 6
[debug_legacy2.cc:18] 2 * sum = 12
[debug_legacy2.cc:19] doubled = 12
std::source_location を利用する(C++20 以降)
std::source_location
は、インスタンスを生成した時点でのファイル名、関数名、行番号、カラム番号を保存しています。
このクラスを関数のデフォルト引数として定義することで、関数呼び出し元の情報を引き渡すことが可能になります。
#include <iostream>
#include <source_location>
template <typename T>
T debug(T value, const std::source_location location = std::source_location::current())
{
std::cerr
<< "[" << location.file_name() << ":" << location.line() << "] "
<< (value) << std::endl;
return value;
}
int main()
{
int sum = debug(1 + 2 + 3);
int doubled = debug(2 * sum);
debug(doubled);
return 0;
}
std::source_location は C++20 以降の機能ですので、コンパイラがこれに対応している必要があります。
g++ であれば、-std=c++20
とオプションを指定することで有効になります。
$ g++ -std=c++20 debug_cpp20.cc && ./a.out
[debug_cpp20.cc:15] 6
[debug_cpp20.cc:16] 12
[debug_cpp20.cc:17] 12
機能不足な点
define マクロをやめて template でデバッグ出力を実現することができました。
しかし、template では、呼び出し元の引数を文字列として扱うことができません。
例えば、前述の例では “1 + 2 + 3” といった評価前の文字列をアウトプットすることができません。
この点では、define マクロの方がまだ優秀なところがあります。
まだまだ define の呪縛からは逃れられなそうです。
コメント