18

このようなヘッダー ファイルで定義されたテンプレート クラスがあります。ここでは、静的変数も定義しています。

#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;
}

次のように、関数 f1() を別の cpp ファイルに実装しました。

void f1()
{
    MyClass<int> a;
    a.f();

    cout<<"F1: " <<count <<"\n";
}

これを VC6 でコンパイルすると、「F1:0 Main:2」という出力が得られました。これはどのように可能ですか?また、静的変数をテンプレートと一緒に使用したい場合、一般的にどのように処理すればよいですか?

4

5 に答える 5

23

ヘッダー ファイルで静的変数を宣言したため、同じ変数の 2 つのコピーを取得しています。このようにグローバル変数を宣言するstaticと、コンパイル単位 (.oファイル) に対してローカルであると言えます。ヘッダーを 2 つのコンパイル単位に含めるため、2 つのコピーを取得しますcount

ここで本当に必要なのは、テンプレート クラスの各インスタンスに関連付けられた静的テンプレート メンバー変数だと思います。次のようになります。

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();
}

これにより、アクセスするコンパイル単位に関係なく、count が使用される前に確実に初期化されます。詳細については、静的初期化順序に関する C++ FAQ を参照してください。

于 2009-03-03T17:28:10.553 に答える
3

静的宣言をヘッダー ファイルに配置すると、各 .cpp ファイルが独自のバージョンの変数を取得します。したがって、2 つの cout ステートメントは異なる変数を出力しています。

于 2009-03-03T17:11:27.677 に答える
1

「F1:1 Main:1」を期待していましたか? 2 つの別々の翻訳単位 (つまり、2 つのオブジェクト ファイル) でインスタンスMyClass<int>化しましたが、リンカーはテンプレートのインスタンス化が重複していることを検出したため、のオブジェクト ファイルにあったインスタンス化を破棄しましたf1

VC6 リンカに渡しています/OPT:ICFか? /OPT:REFこれは、重複したテンプレートのインスタンス化の削除に関連している可能性があります (または関連していない可能性があります。通常の重複した関数と比較して、重複したテンプレートのインスタンス化は特殊な​​ケースである可能性があります)。GCC は、一部のプラットフォームで同様のことを行うようです。

とにかく、この動作がコンパイラ間で一貫していることに依存しません。また、リンカー コマンド ラインでオブジェクト ファイルの順序を変更すると、どのインスタンス化が破棄されるかに影響する場合があります。

于 2009-03-04T07:07:30.380 に答える
0

別の解決策があります。共有の親クラスを作成し、この静的変数をその中に入れてから、テンプレート クラスに非公開で継承させることができます。次に例を示します。

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
于 2016-12-18T15:58:57.373 に答える
-1

これは実際には未定義の動作だと思います。

C++14 [basic.def.odr]/6 によると:

クラス テンプレート [...] の [...] メンバー関数の定義が、プログラム内に複数存在する可能性があります。ただし、各定義が異なる翻訳単位に表示され、定義が次の要件を満たしている場合に限ります。D複数の翻訳単位で定義された名前のエンティティが与えられた場合、

  • D の各定義は、同じ一連のトークンで構成されます。と
  • D の各定義では、対応する名前は、3.4 に従って検索され、D の定義内で定義されたエンティティを参照するか、またはオーバーロードの解決 (13.3) および部分的なテンプレートの特殊化の一致 (14.8) の後に同じエンティティを参照する必要があります。 .3) ただし、オブジェクトが D のすべての定義で同じリテラル型を持ち、オブジェクトが定数式 (5.19) で初期化されている場合、名前は内部リンケージまたはリンケージなしの非揮発性 const オブジェクトを参照できます。オブジェクトは ODR 使用されておらず、オブジェクトは D のすべての定義で同じ値を持っています。[...]

問題は、最初の.cppファイルcount内の名前が 2 番目のファイル内f1の名前とは異なるオブジェクトを参照しているため、対応する名前が同じエンティティを参照する必要があるという条件に違反していることです。countf1.cpp

staticこれらは、各翻訳単位がその名前の独自のオブジェクトを取得することを示す指定子のため、異なるオブジェクトです。

于 2015-11-16T10:26:46.770 に答える