42

静的メンバーの初期化には、ネストされたヘルパー構造体を使用します。これは、テンプレート化されていないクラスで正常に機能します。ただし、包含クラスがテンプレートによってパラメーター化されている場合、メインコードでヘルパーオブジェクトにアクセスしないと、ネストされた初期化クラスはインスタンス化されません。説明のために、簡単な例(私の場合、ベクトルを初期化する必要があります)。

#include <string>
#include <iostream>

struct A
{
    struct InitHelper
    {
        InitHelper()
        {
            A::mA = "Hello, I'm A.";
        }
    };
    static std::string mA;
    static InitHelper mInit;

    static const std::string& getA(){ return mA; }
};
std::string A::mA;
A::InitHelper A::mInit;


template<class T>
struct B
{
    struct InitHelper
    {
        InitHelper()
        {
            B<T>::mB = "Hello, I'm B."; // [3]
        }
    };
    static std::string mB;
    static InitHelper mInit;

    static const std::string& getB() { return mB; }
    static InitHelper& getHelper(){ return mInit; }
};
template<class T>
std::string B<T>::mB; //[4]
template<class T>
typename B<T>::InitHelper B<T>::mInit;


int main(int argc, char* argv[])
{
    std::cout << "A = " << A::getA() << std::endl;

//    std::cout << "B = " << B<int>::getB() << std::endl; // [1]
//    B<int>::getHelper();    // [2]
}

g ++ 4.4.1の場合:

  • [1]と[2]のコメント:

    A =こんにちは、私はAです。

    意図したとおりに動作します

  • [1]コメントなし:

    A =こんにちは、私はAです。
    B =

    InitHelperがmBを初期化することを期待します

  • [1]および[2]コメントなし:
    A =こんにちは、私はAです。
    B =こんにちは、私はBです。
    意図したとおりに動作します
  • [1]コメント、[2]コメントなし:
    [3]の静的初期化段階でのセグメンテーション違反

したがって、私の質問:これはコンパイラのバグですか、それともモニターと椅子の間にあるバグですか?そして後者の場合:洗練された解決策はありますか(つまり、静的初期化メソッドを明示的に呼び出さないでください)?

アップデートI:
これは望ましい動作のようです(ISO / IEC C ++ 2003標準、14.7.1で定義されています):

クラステンプレートのメンバーまたはメンバーテンプレートが明示的にインスタンス化されているか、明示的に特殊化されていない限り、メンバー定義が存在する必要があるコンテキストで特殊化が参照される場合、メンバーの特殊化は暗黙的にインスタンス化されます。特に、静的データメンバーの定義が存在する必要がある方法で静的データメンバー自体が使用されない限り、静的データメンバーの初期化(および関連する副作用)は発生しません。

4

3 に答える 3

39

これは、私がスタックオーバーフローに関する別の質問に答えようとしていたときに、usenetで議論されました:静的データメンバーのインスタンス化のポイント。テストケースを減らし、各シナリオを個別に検討する価値があると思うので、最初にもっと一般的なものを見てみましょう。


struct C { C(int n) { printf("%d\n", n); } };

template<int N>
struct A {
  static C c;
}; 

template<int N>
C A<N>::c(N); 

A<1> a; // implicit instantiation of A<1> and 2
A<2> b;

静的データメンバーテンプレートの定義があります。次の理由により、これはまだデータメンバーを作成しません14.7.1

「...特に、静的データメンバーの定義が存在する必要がある方法で静的データメンバー自体が使用されない限り、静的データメンバーの初期化(および関連する副作用)は発生しません。」

何か(=エンティティ)の定義は、その単語(at)を定義する1つの定義規則に従って、そのエンティティが「使用される」ときに必要になります3.2/2。特に、すべての参照がインスタンス化されていないテンプレート、テンプレートのメンバー、またはsizeofエンティティを「使用」しない式または同様のものからのものである場合(それらは潜在的に評価されていないか、関数としてまだ存在していないため) / member関数自体が使用されます)、そのような静的データメンバーはインスタンス化されません。

静的データメンバーの宣言をインスタンス化することによる暗黙のインスタンス化14.7.1/7-つまり、その宣言を処理するために必要なテンプレートをインスタンス化します。ただし、定義はインスタンス化されません。つまり、初期化子はインスタンス化されず、その静的データメンバーのタイプのコンストラクターは暗黙的に定義されません(使用済みとしてマークされます)。

つまり、上記のコードはまだ何も出力しません。ここで、静的データメンバーの暗黙的なインスタンス化を引き起こしましょう。

int main() { 
  A<1>::c; // reference them
  A<2>::c; 
}

これにより、2つの静的データメンバーが存在しますが、問題は、初期化の順序はどうですか?3.6.2/1簡単に読むと、それが当てはまると思うかもしれません。

「同じトランスレーションユニットの名前空間スコープで定義され、動的に初期化された静的ストレージ期間を持つオブジェクトは、その定義がトランスレーションユニットに表示される順序で初期化される必要があります。」

usenetの投稿で述べられ、この欠陥レポートで説明されているように、これらの静的データメンバーは変換ユニットで定義されていませんが、次のようにインスタンス化ユニットでインスタンス化され2.1/1ます。

翻訳された各翻訳単位は、必要なインスタンス化のリストを作成するために検査されます。[注:これには、明示的に要求されたインスタンス化が含まれる場合があります(14.7.2)。]必要なテンプレートの定義があります。これらの定義を含む翻訳単位のソースが使用可能である必要があるかどうかは、実装によって定義されます。[注:実装は、ソースがここで必要とされないことを保証するために、翻訳された翻訳単位に十分な情報をエンコードすることができます。]インスタンス化ユニットを生成するために、必要なすべてのインスタンス化が実行されます。[注:これらは翻訳された翻訳単位に似ていますが、インスタンス化されていないテンプレートへの参照やテンプレート定義は含まれていません。]インスタンス化が失敗した場合、プログラムは不正な形式になります。

そのようなメンバーのインスタンス化のポイントも実際には重要ではありません。なぜなら、そのようなインスタンス化のポイントは、インスタンス化とその変換ユニットの間のコンテキストリンクであるため14.6.4.1です。インスタンス化は3.2/5、最後の箇条書きの1つの定義規則で指定されているのと同じ意味をインスタンス化に与える必要があります。

順序付けられた初期化が必要な場合は、インスタンス化を台無しにしないように調整する必要がありますが、明示的な宣言を使用します。これは、通常の宣言と実際には変わらないため、明示的な特殊化の領域です。実際、C++0xはその表現を3.6.2次のように変更しました。

静的ストレージ期間を持つ非ローカルオブジェクトの動的初期化は、順序付けされているか、順序付けされていないかのいずれかです。明示的に特殊化されたクラステンプレート静的データメンバーの定義により、初期化が順序付けられました。他のクラステンプレート静的データメンバー(つまり、暗黙的または明示的にインスタンス化された特殊化)には、順序付けられていない初期化があります。


これは、コードにとって次のことを意味します。

  • [1]コメント:静的データメンバーへ[2]の参照が存在しないため、それらの定義(およびのインスタンス化の必要がないため、宣言もインスタンスB<int>化されません)はインスタンス化されません。副作用は発生しません。
  • [1]uncommented:B<int>::getB()が使用され、それ自体がを使用しますB<int>::mB。これには、静的メンバーが存在する必要があります。文字列はmainの前に初期化されます(いずれの場合も、非ローカルオブジェクトの初期化の一部として、そのステートメントの前に)。を使用するB<int>::mInitものはないため、インスタンス化されず、オブジェクトがB<int>::InitHelper作成されないため、コンストラクタが使用されなくなり、コンストラクタが使用されなくなります。これにより、に何かが割り当てられることはありませんB<int>::mB。空の文字列を出力するだけです。
  • [1][2]コメントなし:これがあなたのために働いたことは運です(またはその逆です:))。上で説明したように、初期化呼び出しの特定の順序は必要ありません。VC ++で動作し、GCCで失敗し、clangで動作する可能性があります。わかりません。
  • [1]コメント付き、[2]コメントなし:同じ問題-繰り返しますが、両方の静的データメンバーが使用されます:B<int>::mInitによって使用されB<int>::getHelper、のインスタンス化によりB<int>::mInit、コンストラクターがインスタンス化され、使用されますB<int>::mB-しかし、コンパイラーの場合、この特定の実行では順序が異なります(不特定の動作は、異なる実行間で一貫している必要はありません):B<int>::mInit最初に初期化され、まだ構築されていない文字列オブジェクトで動作します。
于 2009-12-01T12:22:57.770 に答える
5

問題は、静的メンバー変数に与える定義もテンプレートであることです。

template<class T>
std::string B<T>::mB;
template<class T>
typename B<T>::InitHelper B<T>::mInit;

コンパイル中、これは実際には何も定義しません。これは、T が不明であるためです。これはクラス宣言やテンプレート定義のようなもので、コンパイラはコードを生成したりストレージを予約したりしません。

テンプレート クラスを使用すると、後で暗黙的に定義が行われます。segfaulting の場合は B<int>::mInit を使用しないため、作成されることはありません。

解決策は、必要なメンバーを (初期化せずに) 明示的に定義することです。

template<>
typename B<int>::InitHelper B<int>::mInit;

これは基本的に、テンプレート クラスを明示的に定義するのと同じように機能します。

于 2009-11-30T11:32:51.750 に答える
2
  • [1] コメント無しの場合:OKです。static InitHelper B<int>::mInit存在しません。テンプレート クラス (構造体) のメンバーが使用されていない場合、コンパイルされません。

  • [1] と [2] コメントなしの場合: OKです。B<int>::getHelper()使用static InitHelper B<int>::mInitし、mInit存在します。

  • [1] コメント済み、[2] コメントなし: VS2008 で動作します。

于 2009-11-30T11:15:27.903 に答える