web-dev-qa-db-ja.com

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

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

変数は読み取り専用であるため、変数はpublic static constであるため、クラスの外部で一度設定することができますが、もう一度、againいハックのように思えます。

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

170

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

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

};
177

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

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行目がなくてもリンクしますが、初期化子は実行されません。

80
EFraim

C++ 11ソリューション

C++ 11以降、単純に lambda式 を使用して静的クラスメンバーを初期化できます。これは、さまざまな静的メンバー間に構築順序を課す必要がある場合や、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();
20
emkey08

.hファイル内:

class MyClass {
private:
    static int myValue;
};

.cppファイル内:

#include "myclass.h"

int MyClass::myValue = 0;
19
Ant

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

ヘッダーファイル:

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クラスを外部から完全に隠すという利点があり、クラス内に含まれるすべてのものを初期化する必要があります。

14
Douglas Mandell

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;
}
11
bitwise

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

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

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バージョンと比較して、他の構文を導入せずにボイラープレートコードを厳密に削除します。

9

静的コンストラクターの概念は、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;
6
Martin York

コンパイルしようとするとuseclass Elsewhere(from Earwicker's answer

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

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

コンパイルするには、代わりに「a static method with static local variable inside」を使用できます。このようなもの:

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

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

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

4
user260858

これに対する簡単な解決策は次のようになります。

    //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;
    }
4
Shubham

ちょうど同じトリックを解決しました。シングルトンの単一の静的メンバーの定義を指定する必要がありました。しかし、物事をより複雑にします-それを使用しない限り、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
adspx5

GCCが提供する

__attribute__((constructor))

https://gcc.gnu.org/onlinedocs/gcc-4.7.0/gcc/Function-Attributes.html

この属性を使用して静的メソッドにタグを付けると、main()の前にモジュールのロード時に実行されます。

1
jws

もう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;
}
1
Jim Hunziker

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

.hppファイル:

vector<char> const & letters();

.cppファイル:

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

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
Blaisorblade

うわー、私は誰も最も明白な答えを言及しなかったと信じることができず、C#の静的コンストラクターの動作を最も厳密に模倣するもの、つまりそのタイプの最初のオブジェクトが作成されるまで呼び出されません。

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

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

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

以下に、いくつかの擬似コードを示します。これをクラス定義に追加します。

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();
0
ulatekh

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

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;
0
Nick Lewis

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

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


//Foo.cpp
int Foo::hello = 1;
0
Cristián Romo

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
karmasponge

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

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
0
Jobin

これは解決策ですか?

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

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

#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
kriss