none
マクロ(#define)の読み方がわからない RRS feed

  • 質問

  • マクロの使い方で、次のような実装があったのですが、理解ができません。

    templateの特殊化をおこなっているようには理解できるのですが、下記の部分が理解できません。

    OUTPUT(bool,),OUTPUT(double,)は、上記マクロを使用して展開されるということですか?イメージができません。
    また、#undef OUTPUTは、なぜしているのかが理解できません。

    分からない箇所

    	OUTPUT(bool, bret)
    	OUTPUT(double,dnum)
    #undef OUTPUT

    本題

    template <typename T> T& output();
    
    bool bret = true;
    double dnum = 3.14;
    
    #define OUTPUT(ctype, var) \
    	template<> inline ctype& output<ctype>(){	\
    		return var;	\
    	}
    	OUTPUT(bool, bret)
    	OUTPUT(double,dnum)
    #undef OUTPUT
    
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	std::cout << std::boolalpha << output<bool>() << std::endl;
    	std::cout << output<double>()  << std::endl;
    }

    2017年7月12日 7:18

回答

  • 複数行マクロは、コメントが入れられないのでこまりますよね。
    んで、むりやり入れるとこんな感じかもしれません。

    template <typename T> T& output();

    bool bret = true;
    double dnum = 3.14;

    #define OUTPUT(ctype, var) \  ・・・「OUTPUT(T,v)の開始」
        template<> inline ctype& output<ctype>(){\・・・「与えられた型の&で戻す」
            return var; \・・・「与えられた値を」
        }・・・「OUTPUT(T,v)の終了」

        OUTPUT(bool, bret) ・・・(1)「output<bool>()でbool&型のbret=trueを戻す関数を定義」
        OUTPUT(double,dnum)・・・(2)「output<double>()でdouble&型のdnum=3.14を戻す関数を定義」
    #undef OUTPUT・・・OUTPUT(T,v)を破棄してなかった事にする。

    (1)と(2)はOUTPUT()の本体ではなく利用なので、
    コメントアウトしてコンパイルすると動作がわかるかもしれません。

    2017年7月12日 7:52
  • テンプレートで作られたライブラリのコードを読んでいると、よく見るパターンの実装ですね。

    OUTPUT(bool,),OUTPUT(double,)は、上記マクロを使用して展開されるということですか?

    はい、そうです。コンパイル前のプリプロセッサの段階でソースコードが文字列置換処理されることはご存知でしょうか。上記の場合はテンプレートの特殊化コードに置換されますね。

    #undef OUTPUTは、なぜしているのかが理解できません。

    そうしないと、意味不明なコンパイルエラーが出て困るケースがあるからです。例えば:

    ソースコードAでOUTPUTというキーワードを何かのキーワード(enum定数とか、関数名とか)で使っているとします。で、別のヘッダファイルBで#define OUTPUTを定義するとします。最初ソースコードAはヘッダファイルBをincludeしてないのですが、後の修正で追加したincludeによって、ヘッダファイルBが(直接/間接問わず)ソースコードAに取り込まれるとします。するとプリプロセッサは、ソースコードAにもともと記述されていたOUTPUTを、#define OUTPUTによって文字列置換してしまいます。

    // ソースコードAのオリジナルのコード。
    void OUTPUT(char const* text, int length)
    {
        ...何か処理...
    }
    // ヘッダファイルBでのOUTPUTマクロの定義。
    #define OUTPUT(ctype, var) \
        template <> inline ctype& output<ctype>() { return var; }

    // ソースコードAのプリプロセス結果 void template <> inline char const* text& output<char const* text>() { return int length; } { }

    置換結果のソースコードを見ていただければ、C++コードとして意味不明な実装になっていることがおわかりいただけるかと思います。試しにコンパイルしてみると、その意味不明さ加減がわかると思います。

    お行儀の良いコードは、意図しない置換が発生しないように、マクロの影響範囲を最小限にしておくのです。それが#undefを行う理由です。

    VxWorksというOSのSDKのヘッダにはかつて(今も?)#define m_dataという記述があって、それが故にクラス定義でm_dataという名前のメンバ変数を書こうとするとコンパイルエラーになったりして頭に来ていました。





    2017年7月12日 8:43

すべての返信

  • 複数行マクロは、コメントが入れられないのでこまりますよね。
    んで、むりやり入れるとこんな感じかもしれません。

    template <typename T> T& output();

    bool bret = true;
    double dnum = 3.14;

    #define OUTPUT(ctype, var) \  ・・・「OUTPUT(T,v)の開始」
        template<> inline ctype& output<ctype>(){\・・・「与えられた型の&で戻す」
            return var; \・・・「与えられた値を」
        }・・・「OUTPUT(T,v)の終了」

        OUTPUT(bool, bret) ・・・(1)「output<bool>()でbool&型のbret=trueを戻す関数を定義」
        OUTPUT(double,dnum)・・・(2)「output<double>()でdouble&型のdnum=3.14を戻す関数を定義」
    #undef OUTPUT・・・OUTPUT(T,v)を破棄してなかった事にする。

    (1)と(2)はOUTPUT()の本体ではなく利用なので、
    コメントアウトしてコンパイルすると動作がわかるかもしれません。

    2017年7月12日 7:52
  • テンプレートで作られたライブラリのコードを読んでいると、よく見るパターンの実装ですね。

    OUTPUT(bool,),OUTPUT(double,)は、上記マクロを使用して展開されるということですか?

    はい、そうです。コンパイル前のプリプロセッサの段階でソースコードが文字列置換処理されることはご存知でしょうか。上記の場合はテンプレートの特殊化コードに置換されますね。

    #undef OUTPUTは、なぜしているのかが理解できません。

    そうしないと、意味不明なコンパイルエラーが出て困るケースがあるからです。例えば:

    ソースコードAでOUTPUTというキーワードを何かのキーワード(enum定数とか、関数名とか)で使っているとします。で、別のヘッダファイルBで#define OUTPUTを定義するとします。最初ソースコードAはヘッダファイルBをincludeしてないのですが、後の修正で追加したincludeによって、ヘッダファイルBが(直接/間接問わず)ソースコードAに取り込まれるとします。するとプリプロセッサは、ソースコードAにもともと記述されていたOUTPUTを、#define OUTPUTによって文字列置換してしまいます。

    // ソースコードAのオリジナルのコード。
    void OUTPUT(char const* text, int length)
    {
        ...何か処理...
    }
    // ヘッダファイルBでのOUTPUTマクロの定義。
    #define OUTPUT(ctype, var) \
        template <> inline ctype& output<ctype>() { return var; }

    // ソースコードAのプリプロセス結果 void template <> inline char const* text& output<char const* text>() { return int length; } { }

    置換結果のソースコードを見ていただければ、C++コードとして意味不明な実装になっていることがおわかりいただけるかと思います。試しにコンパイルしてみると、その意味不明さ加減がわかると思います。

    お行儀の良いコードは、意図しない置換が発生しないように、マクロの影響範囲を最小限にしておくのです。それが#undefを行う理由です。

    VxWorksというOSのSDKのヘッダにはかつて(今も?)#define m_dataという記述があって、それが故にクラス定義でm_dataという名前のメンバ変数を書こうとするとコンパイルエラーになったりして頭に来ていました。





    2017年7月12日 8:43
  • ぱっと見るとスコープされているように見えて私も一瞬混乱しました。

    "¥"は複数行でマクロを書くための記号なので、それを外して整備すると以下のようになります。

    #define OUTPUT(ctype, var)  template<> inline ctype& output<ctype>(){ return var; }
    OUTPUT(bool, bret)
    OUTPUT(double,dnum)
    #undef OUTPUT

    1行目でマクロを定義、2,3行目でそのマクロを使って展開、4行目で以後そのマクロが間違って使われないようにする、といった動作になります。

    結果として以下のコードに変換されます。

    template<> inline bool& output<bool>(){ return bret; }
    template<> inline double& output<double>(){ return dnum; }

    2017年7月12日 11:58