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

CompilerForm #1Form #2Form #3Form #4
Borland C++ (5.51 & 5.6)Yes
CodeWarrior (7 and 8)YesYesYes
Comeau (4.3.0.1)non-strict onlynon-strict onlynon-strict onlynon-strict only
Digital Mars (8.26-8.37)YesYesYesYes
GCC 2.95Yes
GCC 3.2YesYes
Intel (6 and 7)YesYesYesYes
Visual C++ (4.2 - 7.1)YesYesYes, except 4.2
Watcom (11 and 12)YesYesYes

だそうです。

上記の解決法はこのうち 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

となりました。

コメント





Counter: 22043 (from 2010/06/03), today: 3, yesterday: 10