プリプロセッサ司令¶
プリプロセッサはコンパイルを行う前にソースファイルの変換などを行うプログラムです。
インクルード¶
#include
で指定ファイルを読み込んでその場に展開します。
単純なファイル展開であるためインクルードガードなどが必要となります。
ファイルの指定には "..."
と <...>
の2種類があります。
<...>
は標準のインクルードディレクトリからファイルを検索します。
標準のインクルードディレクトリは一般に次のもので構成されます。
C
およびC++
の標準ライブラリ- 処理系の標準ライブラリ (Windows における
windows.h
など) - オプション指定されたディレクトリ (外部ライブラリなど)
"..."
はカレントディレクトリからファイルを検索します。
見つからなかった場合には <...>
と同様に標準のインクルードディレクトリからファイルを検索します。
マクロ¶
#define
でマクロを定義して文字列置換を行います。
定義されたマクロはソースファイルの末尾まで有効です。
ソースファイルの途中で無効化するには #undef
を使用します。
オブジェクト形式¶
オブジェクト形式のマクロは固定の文字列へ置換されます。
一般に定数として利用されますが、
特別な理由がない限り constexpr
変数の使用が望ましいです。
#define BUFFER_SIZE 256
int main() {
// `char buffer[256];` に置換される
char buffer[BUFFER_SIZE];
return 0;
}
関数形式¶
関数形式のマクロは引数を使用して文字列へ置換されます。
一般に型に依存しない関数として使用されますが、 特別な理由がない限り関数テンプレートの使用が望ましいです。
#include <iostream>
#define ECHO(VALUE) VALUE
int main() {
// `std::cout << 2 << std::endl;` に置換される
std::cout << ECHO(2) << std::endl;
// `std::cout << "abc" << std::endl;` に置換される
std::cout << ECHO("abc") << std::endl;
return 0;
}
意図しない挙動にならないよう置換内容の工夫が必要となることがあります。
#include <iostream>
// VALUE1 と VALUE2 の和
#define SUM(VALUE1, VALUE2) VALUE1 + VALUE2
int main() {
// `1 + 2` に置換される
int a = SUM(1, 2);
// `1 + 2 * 3` に置換される
int b = SUM(1, 2) * 3;
std::cout << a << std::endl; // 3
std::cout << b << std::endl; // 7 (9 にならない)
return 0;
}
意図通りの挙動にするためには次のように括弧で囲む必要があります。
// VALUE1 と VALUE2 の和
#define SUM(VALUE1, VALUE2) (VALUE1 + VALUE2)
他にも次のようなケースがあります。
#include <iostream>
// 条件 EXPECTED が満たされていない時に MESSAGE を出力
// `\` で改行できる
#define ERROR_LOG(EXPECTED, MESSAGE) \
if (!(EXPECTED)) { \
std::cout << MESSAGE << std::endl; \
}
int main() {
// 期待通りの動作
ERROR_LOG(1 > 0, "message1"); // true なので実行されない
ERROR_LOG(1 < 0, "message2"); // false なので実行される
// 展開されるとブロック有り if 文なのでセミコロンがなくてもエラーにならない
ERROR_LOG(false, "message3")
// ブロック無し if 文では制御構造が変化してしまう
if (false)
ERROR_LOG(false, "message4")
else
ERROR_LOG(false, "message5") // 実行されない
return 0;
}
ブロック無し if 文では次のように展開されます。
if (false)
if (!(false)) { std::cout << "message4" << std::endl; }
else
if (!(false)) { std::cout << "message5" << std::endl; }
分かりやすいように整形すると次のようになります。
if (false)
if (!(false)) {
std::cout << "message4" << std::endl;
} else if (!(false)) {
std::cout << "message5" << std::endl;
}
else
が if
の中に入ってしまい、次の if
とつながって else if
となっています。
このような問題を避けるためには 複文マクロ と呼ばれるイディオムを使用します。
#define ERROR_LOG(EXPECTED, MESSAGE) \
do { \
if (!(EXPECTED)) { \
std::cout << MESSAGE << std::endl; \
} \
} while (false)
定義済みマクロ¶
定義済みマクロの一部を紹介します。
一覧は テキストマクロの置換 - cppreference.com を参照してください。
ファイル名と行番号¶
ソースファイルやヘッダファイルの情報として
__FILE__
でファイル名、
__LINE__
で行番号を取得することができます。
ログ出力ではこれらの値を使用するために、 ログ出力関数を関数形式のマクロで定義するのが一般的です。
C++ バージョン¶
__cplusplus
で使用している C++ のバージョンを表す数値を取得できます。
C++11 であれば 201103L
という値に置換されます。
C++ のバージョンに応じて有効/無効を制御するために使用されます。
条件¶
次の司令で条件に応じてコードの有効/無効を分けることができます。
#if
#elif
#else
#endif
#include <iostream>
int main() {
#if true
std::cout << "true" << std::endl; // 有効 (コンパイルされる)
#endif
#if false
std::cout << "false" << std::endl; // 無効 (コンパイル前に削除される)
#endif
#if false
std::cout << "1" << std::endl; // 無効
#elif true
std::cout << "2" << std::endl; // 有効
#else
std::cout << "3" << std::endl; // 無効
#endif
return 0;
}
defined
によってマクロが定義されているかどうかを条件にすることができます。
#include <iostream>
// 条件だけに使用するマクロは置換文字列を空にする
#define SAMPLE_A
int main() {
#if defined SAMPLE_A
std::cout << "A" << std::endl; // 有効
#endif
#if defined SAMPLE_B
std::cout << "B" << std::endl; // 無効
#endif
return 0;
}
#ifdef と #ifndef¶
マクロが定義されているかという条件は多用されるため、
#if defined
の短縮として #ifdef
、
否定 #if !defined
の短縮として #ifndef
という司令があります。
これらを活用してインクルードガードは実現されています。
#ifndef SAMPLE_H_
#define SAMPLE_H_
// 内容
#endif // SAMPLE_H_