テンプレートとともに静的変数を使用する
質問
このようなヘッダーファイルにテンプレートクラスが定義されています。ここで、静的変数も定義しました:
#ifndef TEST1_H_
#define TEST1_H_
void f1();
static int count;
template <class T>
class MyClass
{
public:
void f()
{
++count;
}
};
#endif
そして、次のような別のcppファイルでmain()関数を定義しました:
int main(int argc, char* argv[])
{
MyClass<int> a;
a.f();
f1();
cout<<"Main:" << count << "\n";
return 0;
}
次のような別のcppファイルに関数f1()を実装しました:
void f1()
{
MyClass<int> a;
a.f();
cout<<"F1: " <<count <<"\n";
}
VC6を使用してこれをコンパイルすると、<!> quot; F1:0 Main:2 <!> quot;として出力されました。これはどのように可能ですか?また、テンプレートとともに静的変数を使用する場合、一般的にどのように処理する必要がありますか?
解決
ヘッダーファイルで静的変数を宣言したため、同じ変数の2つのコピーを取得しています。この方法でグローバル変数static
を宣言すると、コンパイル単位(.o
ファイル)に対してローカルであると言います。 2つのコンパイル単位にヘッダーを含めるため、count
の2つのコピーを取得します。
ここで本当に欲しいのは、テンプレートクラスの各インスタンスに関連付けられた静的テンプレートメンバー変数だと思います。次のようになります。
template <class T>
class MyClass
{
// static member declaration
static int count;
...
};
// static member definition
template<class T> int MyClass<T>::count = 0;
これにより、テンプレートのインスタンス化ごとにカウントが取得されます。つまり、MyClass<int>
、MyClass<foo>
、MyClass<bar>
などのカウントがあります。f1()
は次のようになります。
void f1() {
MyClass<int> a;
a.f();
cout<<"F1: " << MyClass<int>::count <<"\n";
}
(テンプレートパラメータに関係なく)MyClassのすべてインスタンス化のカウントが必要な場合は、グローバル変数を使用する必要があります。
ただし、初期化される前にグローバル変数を使用するリスクがあるため、おそらくグローバル変数は直接必要ありません。これを回避するには、カウントへの参照を返すグローバルな静的メソッドを作成します。
int& my_count() {
static int count = 0;
return count;
}
次に、クラス内から次のようにアクセスします。
void f() {
++my_count();
}
これにより、アクセスするコンパイル単位に関係なく、カウントが使用される前に初期化されます。詳細については、静的初期化順序に関するC ++ FAQ をご覧ください。 。
他のヒント
静的宣言をヘッダーファイルに挿入すると、各.cppファイルが独自のバージョンの変数を取得します。そのため、2つのcoutステートメントは異なる変数を出力しています。
<!> quot; F1:1 Main:1 <!> quot;を期待していましたか? MyClass<int>
を2つの個別の翻訳単位(つまり2つのオブジェクトファイル)でインスタンス化しましたが、リンカーはテンプレートのインスタンス化が重複していることを確認したため、f1
のオブジェクトファイルにあったインスタンス化を破棄しました。
/OPT:ICF
または/OPT:REF
を渡しますか VC6リンカーに?これは、重複するテンプレートのインスタンス化の削除に関連する可能性があります(またはそうではありません。重複するテンプレートのインスタンス化は、通常の重複する機能と比較して特別な場合です)。 GCCは、一部のプラットフォームで同様の処理を行うようです。
とにかく、私はこの動作がコンパイラ間で一貫していることに依存しません。また、リンカコマンドラインでオブジェクトファイルの順序を変更すると、どのインスタンス生成が破棄されるかに影響する場合があります。
別の解決策があります。共有親クラスを作成し、この静的変数をその中に入れて、テンプレートクラスにプライベートに継承させることができます。例は次のとおりです。
class Parent
{
protected:
static long count;
};
long Parent::count = 0;
template<typename T>
class TemplateClass: private Parent
{
private:
int mKey;
public:
TemplateClass():mKey(count++){}
long getKey(){return mKey;}
}
int main()
{
TemplateClass<int> obj1;
TemplateClass<double> obj2;
std::cout<<"Object 1 key is: "<<obj1.getKey()<<std::endl;
std::cout<<"Object 2 key is: "<<obj2.getKey()<<std::endl;
return 0;
}
出力は次のようになります。
Object 1 key is: 0
Object 2 key is: 1
これは実際には未定義の動作だと思います。
C ++ 14 [basic.def.odr] / 6によると:
プログラム内のクラステンプレート[...]の[...]メンバー関数には、各定義が異なる翻訳単位で表示され、以下の要件を満たす定義であれば、複数の定義が可能です。 。このような
D
という名前のエンティティが複数の翻訳単位で定義されている場合、
- Dの各定義は、トークンの同じシーケンスで構成されます。そして
- 3.4に従って検索されたDの各定義で、対応する名前は、Dの定義内で定義されたエンティティを参照するか、オーバーロード解決(13.3)および部分テンプレートの一致後、同じエンティティを参照する特殊化(14.8.3)、ただし名前が不揮発性を参照できることを除く オブジェクトがDのすべての定義で同じリテラル型を持ち、オブジェクトが定数式(5.19)で初期化され、オブジェクトがODRで使用されておらず、オブジェクトが同じ値である場合、内部またはリンケージのないconstオブジェクトDのすべての定義において; [...]
問題は、最初の.cpp
ファイルで、count
内の名前f1
が2番目のstatic
ファイル内の<=>内の名前<=>とは異なるオブジェクトを参照しているため、対応する名前が同じエンティティを参照する必要があるという条件。
これらは異なるオブジェクトです。これは、各翻訳単位がその名前で独自のオブジェクトを取得するという<=>指定子のためです。