C言語への感謝の正拳突き 2日目です。
概要
最初タイトルは「再帰的なincludeにおけるマクロの展開」
としていたため、includeを繰り返してfiboの計算でもすんのか?
と思われた方もいるかもしれませんが、そういうことはModern C++のconstexprにお任せするとして、、
今回も、OSSのソースコードを読んでいて勉強になったマクロの使い方を紹介したいと思います。
全然再帰的でない、self includeと呼べるマクロの展開です。
今回見つけてきたのは、こちらのOSSです。uriparser
http://sourceforge.net/p/uriparser/git/ci/master/tree/
Uri.h //メインのヘッダファイル
UriDefsAnsi.h //Ansi向けのパラメータを定義するヘッダファイル
UriDefsUnicode.h //Unicode向けのパラメータを定義するヘッダファイル
Uri.hをincludeすると、その中で機種依存のパラメータを定義した後、Uri.hをself includeします。
説明するのが面倒なので先に具体例をみてみましょう。
具体例(ヘッダ)
Uri.hはincludeすると、Uri.hを2回self includeします。
1回目のself include時はAnsi用のマクロが定義されたUriDefsAnsi.hをincludeしたのち、body部分が展開されます。
2回目のself include時はUnicdoe用のマクロが定義されたUriDefsUnicode.hをincludeしたのち、body部分が展開されます。
/* What encodings are enabled? */
#include "UriDefsConfig.h"
#if (!defined(URI_PASS_ANSI) && !defined(URI_PASS_UNICODE))
/* Include SELF twice */
# ifdef URI_ENABLE_ANSI // ANSIフラグのガード
# define URI_PASS_ANSI 1 // ANSIフラグ
# include "Uri.h" // ...self include!
# undef URI_PASS_ANSI
# endif
# ifdef URI_ENABLE_UNICODE // UNICODEフラグのガード
# define URI_PASS_UNICODE 1 // UNICODEフラグ
# include "Uri.h" // ...self include!
# undef URI_PASS_UNICODE
# endif
/* Only one pass for each encoding */
#elif (defined(URI_PASS_ANSI) && !defined(URI_H_ANSI) \
&& defined(URI_ENABLE_ANSI)) || (defined(URI_PASS_UNICODE) \
&& !defined(URI_H_UNICODE) && defined(URI_ENABLE_UNICODE))
# ifdef URI_PASS_ANSI
# define URI_H_ANSI 1
# include "UriDefsAnsi.h" // ANSI PASSの際に読み込むANSI用マクロ
# else
# define URI_H_UNICODE 1
# include "UriDefsUnicode.h" // UNICODE PASSの際に読み込むUNICODE用マクロ
# endif
素晴らしいですね。私は絶対こんなコードを書きたくないです。
以降で宣言されている構造体と関数の宣言をみてみます。
typedef struct URI_TYPE(TextRangeStruct) {
const URI_CHAR * first; /**< Pointer to first character */
const URI_CHAR * afterLast; /**< Pointer to character after the last one still in */
} URI_TYPE(TextRange); /**< @copydoc UriTextRangeStructA */
void URI_FUNC(FreeQueryList)(URI_TYPE(QueryList) * queryList);
URI_TYPEやURI_FUNCなどのマクロは、UriDefsUnicode.hとUriDefsAnsi.hでそれぞれ定義されており、
簡単に纏めると、Ansiの方は末尾にAをつけて、Unicodeの方は末尾にWをつけます。
マクロ名 Ansi版マクロ Unicode版マクロ
#define URI_TYPE(x) Uri##x##A Uri##x##W
#define URI_FUNC(x) uri##x##A uri##x##W
#define URI_CHAR(x) char wchar_t
これ、どっかのWindowsでみたことあるやつですね。。Uri.hはAnsi版とUnicode版の両方を宣言し、
Ansi版の依存部分をUriDefsAnsi.hに追い出し、Unicode版の依存部分をUriDefsUnicode.hに追い出していると。。
実際にライブラリのシンボルをみてみますと、両方生成されているのが分かりました。
94: 0002616f 95 FUNC GLOBAL DEFAULT 11 uriFreeQueryListA
103: 00026a32 95 FUNC GLOBAL DEFAULT 11 uriFreeQueryListW
471: 0002616f 95 FUNC GLOBAL DEFAULT 11 uriFreeQueryListA
480: 00026a32 95 FUNC GLOBAL DEFAULT 11 uriFreeQueryListW
具体例(ソースコード)
ソースコードのほうもみてみましょう。
/* What encodings are enabled? */
#include <uriparser/UriDefsConfig.h>
#if (!defined(URI_PASS_ANSI) && !defined(URI_PASS_UNICODE))
/* Include SELF twice */
# ifdef URI_ENABLE_ANSI
# define URI_PASS_ANSI 1
# include "UriQuery.c"
# undef URI_PASS_ANSI
# endif
# ifdef URI_ENABLE_UNICODE
# define URI_PASS_UNICODE 1
# include "UriQuery.c"
# undef URI_PASS_UNICODE
# endif
#else
# ifdef URI_PASS_ANSI
# include <uriparser/UriDefsAnsi.h>
# else
# include <uriparser/UriDefsUnicode.h>
# include <wchar.h>
# endif
...
void URI_FUNC(FreeQueryList)(URI_TYPE(QueryList) * queryList) {
while (queryList != NULL) {
URI_TYPE(QueryList) * nextBackup = queryList->next;
free((URI_CHAR *)queryList->key); /* const cast */
free((URI_CHAR *)queryList->value); /* const cast */
free(queryList);
queryList = nextBackup;
}
}
ソースコードのUriQuery.cは、ヘッダファイルUri.hと同様の方法で、UriQuery.cをself includeするようでした。
正直なところヘッダファイルにAnsi依存とUnicode依存を追い出して破綻なく記述できるのは素晴らしいと思いました。
これデバッグする際には片側だけincludeしてデバッグしてたんでしょうかね、気になります。
注意点
(1) この例は正直すっげーと思ったサンプルを例にあげているだけで、
実際にこんなテクニック使ってAnsi版とUnicode版のコード書くのか?と聞かれたら書きたくないです。
(2) マクロへ過度に頼るのは止めましょう。
(3) マクロを使った小手先の共通化や抽象化より、C++のテンプレートのほうが良い場合が多いため、ModernなC++の使用を検討しましょう。
以上