web-dev-qa-db-ja.com

C ++静的メンバーの初期化(内部のテンプレートは楽しい)

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

#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で定義されています):

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

38
Mr. Mr.

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


_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_のため、これはまだデータメンバーを作成しません。

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

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

_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]_コメントなし: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_を初期化します。これは、まだ作成されていない文字列オブジェクトを操作します。

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

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

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

この定義は、テンプレートクラスを使用するときに暗黙的に行われます。セグメンテーション違反の場合、B <int> :: mInitを使用しないため、作成されません。

解決策は、必要なメンバーを明示的に定義することです(初期化せずに):ソースファイルをどこかに配置します。

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

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

4
Gunther Piez
  • [1]コメントのないケース:大丈夫です。 _static InitHelper B<int>::mInit_は存在しません。テンプレートクラスのメンバー(構造体)が使用されていない場合、コンパイルされません。

  • [1]と[2]のコメント化されていないケース:問題ありません。 B<int>::getHelper() use _static InitHelper B<int>::mInit_およびmInitが存在します。

  • [1]コメント、[2]コメントなし:VS2008で機能します。

2
Alexey Malistov