プリプロセッサは、C または C++ のパーサーが宣言、型、名前空間、またはオーバーロードを認識する前に、前処理トークンを変換します。インクルードを展開し、条件付きディレクティブを評価し、マクロ置換を実行します。言語レベルのスコープと型チェックでは安全でない展開を修復できないため、その出力は生成されたソースコードとして扱ってください。
インクルード検索はビルドツールの契約 #
#include "project/header.hpp" と #include <library/header> が指定するのはヘッダー名トークンであり、言語標準が規定するファイルシステムパスではありません。引用符と山括弧での検索順序を含め、ヘッダー名からソースファイルへの対応付けは処理系定義です。一般にツールチェーンは、引用符の場合はインクルード元ファイルのディレクトリと引用符専用パスを検索し、山括弧のヘッダーの場合は設定済みのシステムパスから検索を開始します。実際の順序については、コンパイラーの文書とビルドコマンドを確認してください。
GCC と Clang では、次のコマンドで有効なインクルード検索リストを確認できます。
printf '' | c++ -E -x c++ -v -プロジェクトの規約として、プロジェクトヘッダーには引用符付きの名前を、インストール済みまたは標準ライブラリのヘッダーには山括弧付きの名前を使用します。広範なグローバル -I パスを追加するのではなく、適切なビルドターゲットにサードパーティー依存関係を設定してください。-isystem は検索位置が異なり、診断の扱いを変更する可能性があるため、-I と交換可能ではありません。
すべてのプロジェクトヘッダーには、多重インクルードへの対策が必要です。従来のガードは、準拠する処理系間で移植可能です。
#ifndef PROJECT_WIDGET_HPP
#define PROJECT_WIDGET_HPP
class Widget;
#endif#pragma once は広くサポートされていますが、ISO C または C++ 標準の一部ではありません。サポート対象のツールチェーンで、その依存を許容できる場合にのみ使用してください。
マクロ置換には複数の段階がある #
関数形式マクロの呼び出しでは、パラメーターが # または ## とともに使用される場合を除き、マクロ引数が置換前にマクロ展開されます。その後、置換リストがさらなる展開のために再走査されます。現在展開中のマクロは再帰展開に一時的に使用できないため、直接的な無限再帰が防止されます。
#define STRINGIZE_IMPL(x) #x
#define STRINGIZE(x) STRINGIZE_IMPL(x)
#define VERSION 42
const char* version = STRINGIZE(VERSION); // "42"
最初の層は、展開された VERSION を実装マクロへ渡します。直接 #VERSION とすると "VERSION" が生成されます。文字列化では、プリプロセッサの空白とエスケープ規則に従ってトークンの綴りが保持されます。## によるトークン連結は、有効な前処理トークンを 1 つ生成する必要があります。
#define JOIN_IMPL(a, b) a ## b
#define JOIN(a, b) JOIN_IMPL(a, b)
int JOIN(worker_, VERSION); // worker_42
生成される識別子は慎重に使用してください。連結された名前は通常のコード検索から宣言を隠し、マクロ呼び出しから離れた位置で診断を発生させる可能性があります。
条件付きコンパイルと機能テスト #
条件付きディレクティブは、マクロ置換後の整数定数式を処理します。設定マクロを明示的に検査し、プラットフォームの選択は必要とする境界の近くに配置してください。
#if defined(_WIN32)
# include <windows.h>
#elif defined(__unix__)
# include <unistd.h>
#else
# error "Unsupported platform"
#endif言語機能には、選択した言語モードで公開される場合、__cpp_concepts などの標準化された機能テストマクロを使用します。ライブラリ機能には該当するヘッダーをインクルードし、利用可能な場合は対応する __cpp_lib_* マクロを <version> から検査します。__has_include は、この演算子をサポートする処理系でオプションのヘッダーを選択できますが、ヘッダーが見つかっても、その API または ABI が必要なバージョンと一致する証明にはなりません。
あり得ない設定には #error を使用します。コンパイル前に検出できる組み合わせには、ビルドシステムによる診断を優先してください。ターゲットプラットフォーム、コンパイラー、および依存関係のバージョンはビルドシステムが管理するためです。
安全なマクロ境界 #
言語で操作を表現できる場合は、constexpr 変数、inline 関数、テンプレート、enum、およびスコープ付きの型を優先してください。型チェック、スコープ、オーバーロード解決、およびデバッガーからの可視性が維持されます。
必要性が残るマクロでは、各式パラメーターと式の結果全体を括弧で囲む必要があります。
#define SQUARE(x) ((x) * (x))括弧を使用しても、反復評価が安全になるわけではありません。SQUARE(index++) は 2 回インクリメントします。マクロでは各引数が 1 回だけ評価されるかを文書化する必要があり、その動作を意図していない限り、呼び出し側は副作用のある式を渡すべきではありません。
文へ展開されるマクロには、単一文パターンを使用します。
#define LOG_FAILURE(message) \
do { \
log(message); \
} while (0)このパターンにより、if 文は呼び出しを 1 つの文として扱えます。ただし、C++ のスコープ、型、例外安全性、または同期を提供するものではありません。テキストとしての性質が必要であり、かつ局所化できる移植性、条件付きコンパイル、またはトークン生成の境界にマクロを限定してください。
コメント
GitHub で閲覧・コメントする(投稿にはログインが必要です)