アカベコマイリ

HEAR NOTHING SEE NOTHING SAY NOTHING

プリプロセッサのインデント

C++ におけるプリプロセッサのインデントについて考える。

一般にプリプロセッサは行頭に配置する。これは C 言語の時代から踏襲されてきたルールであり、現在でも最も移植性が高い (= 古い処理系でも安全) 方法だろう。

このルールの問題点は #ifdef のような制御構文がとても読みにくいこと。特に #else#elif が絡むと厄介である。例えば下記のように単純なものでもプリプロセッサと関数ブロックが混在して非常に読みにくい。

void function()
{
#ifdef __TEST_FLAG__
    // 処理
#else
    // 処理
#endif
}

制御構文の中に入ると更に読みにくくなる。

void function(bool isEnable) {
    if (isEnable) {
#ifdef __TEST_FLAG__
        // 処理
#else
        // 処理
#endif
    }
}

この問題を解決する方法としてプリプロセッサのインデントを検討する。

以前仕事で調べたところ C++ ならば Visual Studio 2003 以降、GCC 4.0 以降なら確実にインデントが可能である事を確認した。つまり最新の開発環境ならほぼ利用できると思ってよいだろう。なお GCC の場合 -traditional-cpp オプションで旧式のプリプロセッサ仕様を強制する事も可能。

実際にインデントしてみよう。前述のコードの 2 番目を修正する。ずいぶんと見通しがよくなった。

void function(bool isEnable) {
    if (isEnable) {
        #ifdef __TEST_FLAG__
            // 処理
        #else
            // 処理
        #endif
    }
}

プリプロセッサについてもう一点。以下のような実装だと TEST_FLAG シンボルの処理側に定義した変数への依存があるため、未定義ならコンパイル エラーとなる。

int function(bool isEnable) {
    int result = 0;
    if (isEnable) {
        #ifdef __TEST_FLAG__
            int value = 4;
        #else
            // 処理
        #endif

        result = value; // __TEST_FLAG__ が未定義ならコンパイル エラー
    }

    return result;
}

開発者が常にプリプロセス後のコードを意識していればよいのだけど、そのようなモラルに期待するより暗黙的なチェック機構を設けるほうが健全。例えば以下のように修正する。

int function(bool isEnable) {
    int result = 0;
    if (isEnable) {
        #ifdef __TEST_FLAG__
        {
            int value = 4;
        }
        #else
        {
            // 処理
        }
        #endif

        result = value; // 常にコンパイルエラー
    }

    return result;
}

プリプロセッサの分岐部分をブロック化した。こうすればブロック内で定義した変数が外から参照されることを防げる。プリプロセッサとブロックは組みとなり、プリプロセッサと共有したい変数を外部で宣言するというルールが強制される。

コンパイルすると TEST_FLAG シンボル定義に関わらず常にエラーとなるので value の参照範囲が不適切なことに気づくだろう。

修正する場合、変数 value をブロック外で宣言するか result に変数 value を代入するのではなく直値へ変更することになるだろう。以下は後者の例。

int function(bool isEnable) {
    int result = 0;
    if (isEnable) {
        #ifdef __FLAG_ONE__
        {
            result = 4;
        }
        #else
        {
            // 処理
        }
        #endif
    }

    return result;
}

かつて在籍していたプロジェクトでは今回の記事に書いたルールでプリプロセッサを運用していた。そのプロジェクトではクロス プラットフォーム対応のため #ifdef がよく登場していたのだけど、このルールにより事故を避けられたことが何度かある。

インデントは深くなる。しかしそれが問題になるなら当該部分を関数化すればよいのだし、特にブロックはプリプロセッサの結果を予測することの難しさをうまくカバーしてくれる。なかなかよいルールではなかろうか。