C ++の静的コンストラクタ?プライベート静的オブジェクトを初期化する必要があります


176

プライベート静的データメンバー(すべての文字a〜zを含むベクトル)を持つクラスが必要です。JavaまたはC#では、クラスのインスタンスを作成する前に実行される「静的コンストラクタ」を作成し、クラスの静的データメンバーを設定するだけです。(変数は読み取り専用で、1度だけ設定する必要があるため)1回だけ実行され、クラスの関数であるため、プライベートメンバーにアクセスできます。ベクトルが初期化されているかどうかを確認し、初期化されていない場合は初期化するコードをコンストラクターに追加できますが、これにより多くの必要なチェックが導入され、問題の最適な解決策とは思えません。

変数は読み取り専用であるため、それらはpublic static constである可能性があるため、クラスの外で一度設定できると思いますが、もう一度、それは一種の醜いハックのように思えます。

インスタンスコンストラクターで初期化したくない場合、クラスにプライベート静的データメンバーを含めることはできますか?



1
@CiroSantilli新疆改造中心六四事件法轮功この質問は、プライベート静的プリミティブ型の定数値を設定するのではなく、プライベート静的オブジェクトを初期化するコードの実行に焦点を当てています。ソリューションは異なります。
ゴードングスタフソン、

ああ、私はあなたが正しいと思います、撤回します。
Ciro Santilli郝海东冠状病六四事件法轮功

回答:


180

静的コンストラクタと同等のものを取得するには、静的データを保持する別の通常クラスを記述して、その通常クラスの静的インスタンスを作成する必要があります。

class StaticStuff
{
     std::vector<char> letters_;

public:
     StaticStuff()
     {
         for (char c = 'a'; c <= 'z'; c++)
             letters_.push_back(c);
     }

     // provide some way to get at letters_
};

class Elsewhere
{
    static StaticStuff staticStuff; // constructor runs once, single instance

};

12
ありがとう!それはそれをすべてしなければならないことは非常に迷惑ですが。C#とJavaから学んだ多くの「間違い」の1つ。
ゴードングスタフソン

109
はい。私はいつもC ++がそれらすべての「間違い」を作らなかったなら、他の言語がそれらを作らなければならないであろうことを人々に指摘します。C ++は、多くの根拠をカバーし、ミスを犯したとしても、それに続く言語に最適です。
クォーク

11
わずかなニュアンスが1つあります。コンストラクターが機能するようになると、静的オブジェクトのコンストラクターがいつ実行されるかは誰も保証しません。よく知られているはるかに安全なアプローチは、Elsewhereクラスです。{StaticStuff&get_staticStuff(){static StaticStuff staticStuff; //コンストラクタは1回実行され、誰かが最初にそれを必要とするときにstaticStuff;を返します。}}; C#とJavaの静的コンストラクターが上記のコードと同じ保証を提供できるかどうか疑問に思います...
Oleg Zhylin 09

13
@オレグ:はい、そうです。標準では、すべての非ローカル変数のコンストラクターがmainに入る前に実行されることが保証されています。また、コンパイルユニット内では、構築の順序が適切に定義され、コンパイルユニット内の宣言と同じ順序であることも保証されます。残念ながら、それらは複数のコンパイル単位にわたる順序を定義していません。
マーティンヨーク

13
これは実際に、friendクラスElsewhereStaticStuff(カプセル化を危険な方法で壊すことなく)内部に簡単にアクセスできるようにするために、非常に理にかなっているケースです。
Konrad Rudolph

81

さてあなたは持つことができます

class MyClass
{
    public:
        static vector<char> a;

        static class _init
        {
          public:
            _init() { for(char i='a'; i<='z'; i++) a.push_back(i); }
        } _initializer;
};

これを(.cppで)忘れないでください:

vector<char> MyClass::a;
MyClass::_init MyClass::_initializer;

プログラムは引き続き2行目なしでリンクしますが、初期化子は実行されません。


+1(試しませんでした)しかし:ctor _init._init()はいつ呼び出されますか?静的なMyClassオブジェクトがある場合、MyClassのctorの前または後に?あなたには分からないと思います...
ウル。

2
こんにちは、この「イニシャライザ」マジックの詳細はどこにありますか?
カレルビレク

MyClass::a.push_back(i)代わりにすべきではないa.push_back(i)ですか?
Neel Basu、2011

4
@ur .:はの_initializerサブオブジェクトですMyClass。サブオブジェクトはこの順序で初期化されます。仮想基本クラスのサブオブジェクトは、深さ優先、左から右の順序です(ただし、個別のサブオブジェクトはそれぞれ1回だけ初期化します)。次に、基本クラスの単純なサブオブジェクトを深さ優先、左から右の順序で; 次に、宣言順にメンバーサブオブジェクトを作成します。したがって、EFraimの戦略は、その_initialiser前に宣言されたメンバーのみをコードで参照する場合に限り、安全です。
j_random_hacker

2
この答えは、著者が2番目のコードクリップで必須の初期化に言及しているため、受け入れられたものよりも優れています。
Jeff T.

33

C ++ 11ソリューション

C ++ 11以降、ラムダ式を使用して静的クラスメンバーを初期化できます。これは、さまざまな静的メンバー間に構築順序を課す必要がある場合や、静的メンバーがである場合にも機能しますconst

ヘッダーファイル:

class MyClass {
    static const vector<char> letters;
    static const size_t letterCount;
};

ソースファイル:

// Initialize MyClass::letters by using a lambda expression.
const vector<char> MyClass::letters = [] {
    vector<char> letters;
    for (char c = 'a'; c <= 'z'; c++)
        letters.push_back(c);
    return letters;
}();

// The initialization order of static members is defined by the order of
// definition within the source file, so we can access MyClass::letters here.
const size_t MyClass::letterCount = letters.size();

興味深い解決策。この場合、それをキャッチできる例外をスローするとどうなりますか?
rafi wiener

5
静的プログラム初期化コードは例外をスローしてはなりませ。そうしないと、プログラムがクラッシュします。try catch例外がスローされる可能性がある場合は、初期化ロジックをブロックにラップする必要があります。
emkey08

19

.hファイル:

class MyClass {
private:
    static int myValue;
};

.cppファイル:

#include "myclass.h"

int MyClass::myValue = 0;

5
これは、(型に関係なく)個々の静的メンバーに対して正常に機能します。静的コンストラクターと比較した場合の欠点は、さまざまな静的メンバー間に順序を課すことができないことです。それを行う必要がある場合は、Earwickerの回答を参照してください。
クォーク

私はまさにそれをやっていますが、それでもコンパイルされません。そして、これは問題の領域であると言います(ヘッダーではなくコンストラクター内)
Flotolk

14

これもダニエル・イアウィッカーと同様のアプローチで、コンラッド・ルドルフの友人クラスの提案も使用しています。ここでは、内部プライベートフレンドユーティリティクラスを使用して、メインクラスの静的メンバーを初期化します。例えば:

ヘッダーファイル:

class ToBeInitialized
{
    // Inner friend utility class to initialize whatever you need

    class Initializer
    {
    public:
        Initializer();
    };

    friend class Initializer;

    // Static member variables of ToBeInitialized class

    static const int numberOfFloats;
    static float *theFloats;

    // Static instance of Initializer
    //   When this is created, its constructor initializes
    //   the ToBeInitialized class' static variables

    static Initializer initializer;
};

実装ファイル:

// Normal static scalar initializer
const int ToBeInitialized::numberOfFloats = 17;

// Constructor of Initializer class.
//    Here is where you can initialize any static members
//    of the enclosing ToBeInitialized class since this inner
//    class is a friend of it.

ToBeInitialized::Initializer::Initializer()
{
    ToBeInitialized::theFloats =
        (float *)malloc(ToBeInitialized::numberOfFloats * sizeof(float));

    for (int i = 0; i < ToBeInitialized::numberOfFloats; ++i)
        ToBeInitialized::theFloats[i] = calculateSomeFancyValue(i);
}

このアプローチには、Initializerクラスを外部から完全に隠し、クラス内に含まれるすべてのものを初期化しておくという利点があります。


+1実装を独自のファイルに保持する例を示すため。
Andrew Larsson、

1
また、それToBeInitialized::Initializer::Initializer()が呼び出されることを確認する必要があるToBeInitialized::Initializer ToBeInitialized::initializer;ため、実装ファイルに追加する必要があります。私はあなたのアイデアとEFraimのアイデアからいくつかのものを取りましたが、それは私が必要とするとおりに機能し、きれいに見えます。ありがとう。
Andrew Larsson

11

Test::StaticTest() グローバル静的初期化中に1回だけ呼び出されます。

呼び出し側は、静的コンストラクターになる関数に1行追加するだけです。

static_constructor<&Test::StaticTest>::c;cグローバル静的初期化中に強制的に初期化します。

template<void(*ctor)()>
struct static_constructor
{
    struct constructor { constructor() { ctor(); } };
    static constructor c;
};

template<void(*ctor)()>
typename static_constructor<ctor>::constructor static_constructor<ctor>::c;

/////////////////////////////

struct Test
{
    static int number;

    static void StaticTest()
    {
        static_constructor<&Test::StaticTest>::c;

        number = 123;
        cout << "static ctor" << endl;
    }
};

int Test::number;

int main(int argc, char *argv[])
{
    cout << Test::number << endl;
    return 0;
}

これは素晴らしいソリューションです。ダグラス・マンデルの答えも本当に好きですが、これはさらに簡潔です。
FlintZA 2016

これは本当に素晴らしいです!
nh_

9

init()関数は不要で、次std::vectorの範囲から作成できます。

// h file:
class MyClass {
    static std::vector<char> alphabet;
// ...
};

// cpp file:
#include <boost/range.hpp>
static const char alphabet[] = "abcdefghijklmnopqrstuvwxyz";
std::vector<char> MyClass::alphabet( boost::begin( ::alphabet ), boost::end( ::alphabet ) );

ただし、クラス型のstaticはライブラリに問題を引き起こすため、そこで回避する必要があることに注意してください。

C ++ 11アップデート

C ++ 11以降では、代わりにこれを行うことができます:

// cpp file:
std::vector<char> MyClass::alphabet = { 'a', 'b', 'c', ..., 'z' };

意味的には元の回答のC ++ 98ソリューションと同等ですが、右側に文字列リテラルを使用できないため、完全に優れているわけではありません。もし以外のタイプのベクトルを有する場合は、charwchar_tchar16_tまたはchar32_t(文字列リテラルとして記述することができるの配列)、C ++ 11のバージョンは、厳密にはC ++ 98と比較して、他の構文を導入することなく、定型的なコードを削除しますバージョン。


私はそれが好きです。でも今は無用のアルファベットなしで一行でそれができたとしたら。
マーティンヨーク

ライブラリの問題を引き起こすために、静的クラスがプライベートであるかパブリックであるかは重要ですか?さらに、ライブラリが静的(.a)か動的(.so)かは重要ですか?
ザカリークラウス2015年

@ZacharyKraus:public / private クラスとは何ですか?いいえ、問題は異なりますが重複していますが、ライブラリが静的にリンクされているか動的にリンクされているかは問題ではありません。
マルク・ムッツ-mmutz 2015年

@ MarcMutz-mmutz正しいC ++用語ではないpublic / privateクラスを使用して申し訳ありません。私が言及していたのは、上記のEFraimによるソリューションです。私のバージョンでは、静的クラスのメンバーをプライベートにしました。静的クラスメンバーをパブリックまたはプライベートとして持つと、ライブラリの開発と使いやすさが変わるかどうかを理解しようとしました。静的なクラスメンバーやそのオブジェクトのオブジェクトにユーザーがアクセスすることはできないため、ライブラリには影響しないはずですが、このトピックについてグルの知恵を手に入れたいと思います。
ザカリークラウス

@ZacharyKraus:動的初期化([basic.start.init] / 2)を必要とするstaticの主な問題は、コードを実行することです。ライブラリでは、デストラクタの実行時にライブラリコードがすでにアンロードされている可能性があります。もっと聞きたい場合は、それについて質問を投稿することをお勧めします。
Marc Mutz-mmutz 2015年

6

静的コンストラクタの概念は、C ++の問題から学んだ後にJavaで導入されました。したがって、直接対応するものはありません。

最良の解決策は、明示的に初期化できるPODタイプを使用することです。
または、静的メンバーを、正しく初期化する独自のコンストラクターを持つ特定の型にします。

//header

class A
{
    // Make sure this is private so that nobody can missues the fact that
    // you are overriding std::vector. Just doing it here as a quicky example
    // don't take it as a recomendation for deriving from vector.
    class MyInitedVar: public std::vector<char>
    {
        public:
        MyInitedVar()
        {
           // Pre-Initialize the vector.
           for(char c = 'a';c <= 'z';++c)
           {
               push_back(c);
           }
        }
    };
    static int          count;
    static MyInitedVar  var1;

};


//source
int            A::count = 0;
A::MyInitedVar A::var1;

4

Earwickerの答えから)クラスをコンパイルして使用しようとすると、次のようになります:Elsewhere

error LNK2001: unresolved external symbol "private: static class StaticStuff Elsewhere::staticStuff" (?staticStuff@Elsewhere@@0VStaticStuff@@A)

クラス定義(CPP)の外にコードを配置しないと、非整数型の静的属性を初期化することはできないようです。

そのコンパイルを行うには、代わり内部に静的ローカル変数を持つ静的メソッド」を使用できます。このようなもの:

class Elsewhere
{
public:
    static StaticStuff& GetStaticStuff()
    {
        static StaticStuff staticStuff; // constructor runs once, single instance
        return staticStuff;
    }
};

また、コンストラクタに引数を渡したり、特定の値で初期化したりすることもできます。非常に柔軟で強力で、実装も簡単です...静的属性ではなく、静的変数を含む静的メソッドがあるだけです... syntaxisは少し変更されますが、それでも有用です。これが誰かに役立つことを願って、

ヒューゴ・ゴンサレス・カストロ。


ただし、スレッドを使用する場合は注意が必要です。GCCでは静的ローカルの構築は同時実行から保護されていると思いますが、Visual C ++ではそうではありません。
Daniel Earwicker、2011年

1
C ++ 11以降、およびPOSIXで、それから持っているスレッドセーフであることを。
マルク・ムッツ-mmutz 2015年

上記の他の2つのソリューション(thisthis)は非常に気に入りましたが、ライブラリ全体で必要な順序で静的変数を初期化できる唯一の方法はあなたのものです。上記のようなプライベート静的インスタンスメソッドがあり、直接参照の代わりにそのインスタンスメソッドを使用するパブリック静的アクセサーで他の値へのアクセスをラップします。ありがとう。
FlintZA 2016

驚くばかり!これで完了です。
Gabe Halsmer、2016

4

これに対する簡単な解決策は次のようになると思います:

    //X.h
    #pragma once
    class X
    {
    public:
            X(void);
            ~X(void);
    private:
            static bool IsInit;
            static bool Init();
    };

    //X.cpp
    #include "X.h"
    #include <iostream>

    X::X(void)
    {
    }


    X::~X(void)
    {
    }

    bool X::IsInit(Init());
    bool X::Init()
    {
            std::cout<< "ddddd";
            return true;
    }

    // main.cpp
    #include "X.h"
    int main ()
    {
            return 0;
    }

これも私のやり方です。
Etherealone

1

ちょうど同じトリックを解決しました。シングルトンの単一の静的メンバーの定義を指定する必要がありました。しかし、物事をより複雑にします-それを使用するつもりがない限り、RandClass()のctorを呼び出さないことにしました...そのため、コードでグローバルにシングルトンを初期化したくありませんでした。また、私の場合はシンプルなインターフェースを追加しました。

これが最終的なコードです:

私はコードを簡略化し、rand()関数とその単一シードのイニシャライザーsrand()を使用しました

interface IRandClass
{
 public:
    virtual int GetRandom() = 0;
};

class RandClassSingleton
{
private:
  class RandClass : public IRandClass
  {
    public:
      RandClass()
      {
        srand(GetTickCount());
      };

     virtual int GetRandom(){return rand();};
  };

  RandClassSingleton(){};
  RandClassSingleton(const RandClassSingleton&);

  // static RandClass m_Instance;

  // If you declare m_Instance here you need to place
  // definition for this static object somewhere in your cpp code as
  // RandClassSingleton::RandClass RandClassSingleton::m_Instance;

  public:

  static RandClass& GetInstance()
  {
      // Much better to instantiate m_Instance here (inside of static function).
      // Instantiated only if this function is called.

      static RandClass m_Instance;
      return m_Instance;
  };
};

main()
{
    // Late binding. Calling RandClass ctor only now
    IRandClass *p = &RandClassSingleton::GetInstance();
    int randValue = p->GetRandom();
}
abc()
{
    IRandClass *same_p = &RandClassSingleton::GetInstance();
}

1

これがEFraimのソリューションの変形です。違いは、暗黙的なテンプレートのインスタンス化のおかげで、クラスのインスタンスが作成された場合にのみ静的コンストラクターが呼び出され、.cppファイル内での定義が不要になることです(テンプレートのインスタンス化の魔法のおかげ)。

では.h、ファイル、あなたが持っています:

template <typename Aux> class _MyClass
{
    public:
        static vector<char> a;
        _MyClass() {
            (void) _initializer; //Reference the static member to ensure that it is instantiated and its initializer is called.
        }
    private:
        static struct _init
        {
            _init() { for(char i='a'; i<='z'; i++) a.push_back(i); }
        } _initializer;

};
typedef _MyClass<void> MyClass;

template <typename Aux> vector<char> _MyClass<Aux>::a;
template <typename Aux> typename _MyClass<Aux>::_init _MyClass<Aux>::_initializer;

では.cpp、ファイル、あなたが持つことができます。

void foobar() {
    MyClass foo; // [1]

    for (vector<char>::iterator it = MyClass::a.begin(); it < MyClass::a.end(); ++it) {
        cout << *it;
    }
    cout << endl;
}

MyClass::a[1]が存在する場合にのみ初期化されることに注意してください_initializer。これはコンストラクターを呼び出し(およびインスタンス化が必要)、そのためにのインスタンス化が必要になるためです。


1

これは、匿名の名前空間を使用して、実装が含まれるファイルに対してベクターがプライベートである別の方法です。これは、実装専用のルックアップテーブルなどに役立ちます。

#include <iostream>
#include <vector>
using namespace std;

namespace {
  vector<int> vec;

  struct I { I() {
    vec.push_back(1);
    vec.push_back(3);
    vec.push_back(5);
  }} i;
}

int main() {

  vector<int>::const_iterator end = vec.end();
  for (vector<int>::const_iterator i = vec.begin();
       i != end; ++i) {
    cout << *i << endl;
  }

  return 0;
}

名前を付けIたりi、もう少しあいまいにしたりして、ファイルのどこか下の場所で誤って使用しないようにすることもできます。
ジムハンジカー

1
正直に言うと、実装ファイルで匿名の名前空間ではなくプライベート静的メンバーを使用する理由を理解するのは困難です。
ジムハンジカー

1

確かに、現在受け入れられている回答(Daniel Earwickerによる)ほど複雑である必要はありません。クラスは不必要です。この場合、言語戦争の必要はありません。

.hppファイル:

vector<char> const & letters();

.cppファイル:

vector<char> const & letters()
{
  static vector<char> v = {'a', 'b', 'c', ...};
  return v;
}


0

メンバーメソッドを定義する方法と同様に、静的メンバー変数を定義します。

foo.h

class Foo
{
public:
    void bar();
private:
    static int count;
};

foo.cpp

#include "foo.h"

void Foo::bar()
{
    // method definition
}

int Foo::count = 0;

2
CrazyJugglerDrummerの質問は、静的なプレーンな古いデータ型ではありませんでした :)
jww

0

静的変数を初期化するには、ソースファイル内で初期化するだけです。例えば:

//Foo.h
class Foo
{
 private:
  static int hello;
};


//Foo.cpp
int Foo::hello = 1;

CrazyJugglerDrummerの質問は、静的なプレーンな古いデータ型ではありませんでした :)
jww

0

C#の動作を模倣するテンプレートを作成してみませんか。

template<class T> class StaticConstructor
{
    bool m_StaticsInitialised = false;

public:
    typedef void (*StaticCallback)(void);

    StaticConstructor(StaticCallback callback)
    {
        if (m_StaticsInitialised)
            return;

        callback();

        m_StaticsInitialised = true;
    }
}

template<class T> bool StaticConstructor<T>::m_StaticsInitialised;

class Test : public StaticConstructor<Test>
{
    static std::vector<char> letters_;

    static void _Test()
    {
        for (char c = 'a'; c <= 'z'; c++)
            letters_.push_back(c);
    }

public:
    Test() : StaticConstructor<Test>(&_Test)
    {
        // non static stuff
    };
};

0

このような単純なケースでは、静的メンバー関数内にラップされた静的変数もほぼ同じです。これは単純で、通常はコンパイラーによって最適化されます。ただし、これは複雑なオブジェクトの初期化順序の問題を解決しません。

#include <iostream>

class MyClass 
{

    static const char * const letters(void){
        static const char * const var = "abcdefghijklmnopqrstuvwxyz";
        return var;
    }

    public:
        void show(){
            std::cout << letters() << "\n";
        }
};


int main(){
    MyClass c;
    c.show();
}

0

これは解決策ですか?

class Foo
{
public:
    size_t count;
    Foo()
    {
        static size_t count = 0;
        this->count = count += 1;
    }
};

0

静的コンストラクタは、以下のようにフレンドクラスまたはネストされたクラスを使用してエミュレートできます。

class ClassStatic{
private:
    static char *str;
public:
    char* get_str() { return str; }
    void set_str(char *s) { str = s; }
    // A nested class, which used as static constructor
    static class ClassInit{
    public:
        ClassInit(int size){ 
            // Static constructor definition
            str = new char[size];
            str = "How are you?";
        }
    } initializer;
};

// Static variable creation
char* ClassStatic::str; 
// Static constructor call
ClassStatic::ClassInit ClassStatic::initializer(20);

int main() {
    ClassStatic a;
    ClassStatic b;
    std::cout << "String in a: " << a.get_str() << std::endl;
    std::cout << "String in b: " << b.get_str() << std::endl;
    a.set_str("I am fine");
    std::cout << "String in a: " << a.get_str() << std::endl;
    std::cout << "String in b: " << b.get_str() << std::endl;
    std::cin.ignore();
}

出力:

String in a: How are you?
String in b: How are you?
String in a: I am fine
String in b: I am fine

newchar配列を使用して、ポインタをすぐにリークして上書きするのはなぜですか?
エリック

0

うわー、私は誰も最も明白な答えを述べたとは信じられません。C#の静的コンストラクタの動作を最もよく模倣したもの、つまり、その型の最初のオブジェクトが作成されるまで呼び出されません。

std::call_once()C ++ 11で利用可能です。それを使用できない場合は、静的なブールクラス変数と比較および交換のアトミック操作を使用して実行できます。コンストラクターで、クラス静的フラグをからfalseにアトミックに変更できるかどうかを確認します。変更できるtrue場合は、静的構成コードを実行できます。

追加のクレジットについては、ブール値ではなく、3ウェイフラグにします。つまり、実行、実行、実行を終了しません。次に、そのクラスの他のすべてのインスタンスは、静的コンストラクタを実行しているインスタンスが終了するまでスピンロックできます(つまり、メモリフェンスを発行し、状態を「実行中」に設定します)。スピンロックは、プロセッサの「一時停止」命令を実行し、しきい値になるまで毎回2倍待機する必要があります。これはかなり標準的なスピンロックテクニックです。

C ++ 11がない場合、これで開始できます。

ここにあなたを導くいくつかの疑似コードがあります。これをクラス定義に入れます:

enum EStaticConstructor { kNotRun, kRunning, kDone };
static volatile EStaticConstructor sm_eClass = kNotRun;

そしてこれはあなたのコンストラクタで:

while (sm_eClass == kNotRun)
{
    if (atomic_compare_exchange_weak(&sm_eClass, kNotRun, kRunning))
    {
        /* Perform static initialization here. */

        atomic_thread_fence(memory_order_release);
        sm_eClass = kDone;
    }
}
while (sm_eClass != kDone)
    atomic_pause();
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.