プログラミング/C++/C++テクニック の履歴(No.3)
更新概要†
C++ は長らく触っていなかったので、 えっ、と思うようなことを知らずに苦労しています。
覚えた内容をここにメモ。
テンプレート引数クラスへの friend 指定†
cygwin 上の g++ (GCC) 4.3.4 で、
LANG:cpp(linenumber) template<class T> class Test { friend class T; };
としたところ friend の行で
LANG:console $ g++ test.cpp test.cpp:4: error: using template type parameter 'T' after 'class' test.cpp:4: error: friend declaration does not name a class or function
というエラーが出てしまいました。
テンプレート引数となった型 T に対して、 正攻法では friend 関係を構築できないようなのです。
これは C++ の仕様らしいのですが、これを回避するための方法として、 以下を見つけました。
LANG:cpp(linenumber) template<class T> class Test { struct alias_maker { typedef T T_alias; }; friend class alias_maker::T_alias; };
凄く小手先なんですが・・・いいんですかね、これで。
この件について http://www.byte.com/documents/s=9162/cujexp0312wilson2/ に詳しい解説があったようです。
今は見られなくなっていたので webarchive 経由で見たところ、http://replay.waybackmachine.org/20041212160407/http://www.byte.com/documents/s=9162/cujexp0312wilson2/ この回避方法および、その他の回避方法に関する互換性情報がまとめられていました。
Compiler | Form #1 | Form #2 | Form #3 | Form #4 |
Borland C++ (5.51 & 5.6) | Yes | |||
CodeWarrior (7 and 8) | Yes | Yes | Yes | |
Comeau (4.3.0.1) | non-strict only | non-strict only | non-strict only | non-strict only |
Digital Mars (8.26-8.37) | Yes | Yes | Yes | Yes |
GCC 2.95 | Yes | |||
GCC 3.2 | Yes | Yes | ||
Intel (6 and 7) | Yes | Yes | Yes | Yes |
Visual C++ (4.2 - 7.1) | Yes | Yes | Yes, except 4.2 | |
Watcom (11 and 12) | Yes | Yes | Yes |
だそうです。
上記の解決法はこのうち Form #3 にあたり、
何にも考えずに friend T と書くのが Form #1、
何にも考えずに friend class T と書くのが Form #2、
friend_maker をテンプレートクラスにしてもう一段ややこしくしたのが Form #4
です。
リンク先では #ifdef を使って Form #1 と Form #3 とを選んで使うコード例も挙げられていました。
ローカルな static 変数の初期値†
cygwin 上の g++ (GCC) 4.3.4 で、
LANG:cpp(linenumber) int test() { static int i = 0; return i; } #include <iostream> int main(int argc, const char* argv[]) { std::cout << test() << std::endl; }
の結果が、
LANG:console $ g++ test.cpp $ ./a.exe 67600
となって困っています。
納得できないことに、3 行目を i = 0 ではなく i = 1 にして、
LANG:cpp(linenumber) int test() { static int i = 1; return i; }
では正しく 1 が表示されます。
LANG:console $ g++ test.cpp $ ./a.exe 1
初期値が 0 の時のみ、ローカル static 変数の初期化がうまく行っていないのです。
ちなみに、i に初期値を与えず static int i; としても、 結果は 0 を与えたときと同じでした。
そういう仕様でしたっけ???
もしかしたら cygwin のローダーがおかしい???
static コンストラクタのようなもの†
C# には static コンストラクタというのがあって、 static メンバーの初期化等に便利に使えます。
C++ には static コンストラクタはないので、 代替法を探したところ、 http://cppdiary.blog76.fc2.com/blog-entry-11.html を見つけました。
グローバル変数の初期化タイミングで static 関数を呼び出そうという企みのようです。
似たようなことを次のようにしてやってみました。
initial_callback.h
LANG:cpp(linenumber) #ifndef INITIAL_CALLBACK_H #define INITIAL_CALLBACK_H template<class T, void func()> class InitialCallback { public: InitialCallback() { func(); } }; #define INITIAL_CALLBACK(t, f) \ InitialCallback<t, t::f> t##_InitialCallback_##f; #endif
を作って、次のように使います。
initial_callback_test.cpp
LANG:cpp(linenumber) #include "initial_callback.h" #include <iostream> class Square { // InitialCallback を使って table を初期化する static int table[256]; static void StaticInitialize() { for(int i = 0; i < sizeof(table); i++) table[i] = i*i; } public: static int square(int i) { return table[i]; } }; int Square::table[256]; // この宣言で Square::StaticInitialize が初期化時に呼び出されます INITIAL_CALLBACK(Square, StaticInitialize); int main(int argc, const char* argv[]) { std::cout << Square::square(1) << std::endl; std::cout << Square::square(2) << std::endl; std::cout << Square::square(3) << std::endl; std::cout << Square::square(4) << std::endl; }
ミソは 21 行目の INITIAL_CALLBACK という宣言です。
ここにクラス名と static 関数名を与えておくと、 グローバル変数の初期化タイミングで、与えた関数が呼び出されます。
実行してみると、ちゃんと Square::table は初期化されて、
LANG:console $ g++ static_initializer_test.cpp $ ./a.exe 1 4 9 16
となりました。