プログラミング/C++/C++テクニック のバックアップ差分(No.4)

更新


  • 追加された行はこの色です。
  • 削除された行はこの色です。
[[公開メモ]]

#contents

* 概要 [#va06a887]

C++ は長らく触っていなかったので、
えっ、と思うようなことを知らずに苦労しています。

覚えた内容をここにメモ。

* テンプレート引数クラスへの friend 指定 [#b7e0f054]

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/ 
この回避方法および、その他の回避方法に関する互換性情報がまとめられていました。

||||BGCOLOR(YELLOW):||c
|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 変数の初期値 [#s970d922]

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 コンストラクタのようなもの [#sd28d8f7]

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

となりました。

** スコープによってはうまく行かない [#j738e917]

あう、入れ子クラスにこれを使おうとするとうまく行きませんね・・・

 LANGUAGE:C++
 INITIAL_CALLBACK(SuperClass::Square, StaticInitialize);
 
 //  InitialCallback<SuperClass::Square, SuperClass::Square::StaticInitialize>
 //          SuperClass::Square_InitialCallback_StaticInitialize;
 // に展開されて、
 //          SuperClass::Square_InitialCallback_StaticInitialize
 // が見つからないといってエラーになります。

その場合にはマクロを使わず、次のように書けばいいですね。

 LANGUAGE:C++
 InitialCallback<SuperClass::Square, SuperClass::Square::StaticInitialize>
         SomeUniqueName;
 
SomeUniqueName のところは他とかぶらなければどんな名前でも大丈夫です。

* コメント [#m531470d]

#article_kcaptcha


Counter: 18677 (from 2010/06/03), today: 5, yesterday: 1