statyczne konstruktory w C++? Muszę zainicjować prywatne obiekty statyczne

Chcę mieć klasę z prywatnym statycznym członkiem danych (wektor, który zawiera wszystkie znaki a-z). W Javie lub C# mogę po prostu stworzyć "statyczny konstruktor", który uruchomi się przed utworzeniem jakichkolwiek instancji klasy i ustawi statyczne elementy danych klasy. Zostanie uruchomiony tylko raz (ponieważ zmienne są tylko do odczytu i muszą być ustawione tylko raz), a ponieważ jest funkcją klasy, może uzyskać dostęp do swoich prywatnych członków. Mogę dodać kod w konstruktorze, który sprawdza czy vector jest inicjalizowany i inicjalizuje go, jeśli nie jest, ale to wprowadza wiele niezbędnych kontroli i nie wydaje się optymalnym rozwiązaniem problemu.

Przychodzi mi do głowy myśl, że skoro zmienne będą tylko odczytywane, mogą być po prostu public static const, więc mogę ustawić je raz poza klasą, ale po raz kolejny, wydaje się to trochę jak brzydki hack.

Czy możliwe jest posiadanie prywatnych statycznych członków danych w klasie, jeśli nie chcę inicjować ich w instancji konstruktor?

Author: peterh, 2009-07-29

21 answers

Aby uzyskać odpowiednik statycznego konstruktora, musisz napisać oddzielną zwykłą klasę do przechowywania statycznych danych, a następnie utworzyć statyczną instancję tej zwykłej klasy.

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

};
 167
Author: Daniel Earwicker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-29 10:59:28

Cóż możesz mieć

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

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

Nie zapomnij (w .cpp) to:

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

Program nadal będzie się łączył bez drugiej linii, ale inicjalizacja nie zostanie wykonana.

 72
Author: EFraim,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-12 13:43:30

W .plik h:

class MyClass {
private:
    static int myValue;
};

W .plik cpp:

#include "myclass.h"

int MyClass::myValue = 0;
 19
Author: Ant,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-28 22:29:43

Oto inne podejście podobne do Daniela Earwickera, również z wykorzystaniem sugestii Konrada Rudolpha o klasie przyjaciół. Tutaj używamy wewnętrznej prywatnej klasy użytkowej friend do inicjalizacji statycznych członków Twojej głównej klasy. Na przykład:

Plik nagłówkowy:

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

Plik implementacji:

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

To podejście ma tę zaletę, że całkowicie ukrywa klasę Inicjalizatora przed światem zewnętrznym, zachowując wszystko, co zawarte w klasie ma być inicjalizowane.

 15
Author: Douglas Mandell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-20 00:10:35

Statyczna inicjalizacja członka (od C++11)

Od C++11 statyczna inicjalizacja członów może być po prostu zaimplementowana za pomocą funkcji lambda.

Plik nagłówka:

class MyClass {
    static std::vector<char> letters;
};

Plik źródłowy:

std::vector<char> MyClass::letters = [] {
    std::vector<char> letters;
    for (char c = 'a'; c <= 'z'; c++)
        letters.push_back(c);
    return letters;
}();

Konstruktor klasy statycznej (od C++11)

Od C++11, statyczne konstruktory klas (inicjujące wiele różnych elementów statycznych) mogą być również zaimplementowane za pomocą funkcji lambda.

Plik nagłówka:

class MyClass {
    static Foo staticMember1;
    static Foo staticMember2;
    static const bool StaticCtor;    // Note: must be the last static member
};

Plik źródłowy:

Foo MyClass::staticMember1;
Foo MyClass::staticMember2;

const bool MyClass::StaticCtor = [] {
    staticMember1 = ... ;
    staticMember2 = ... ;
    return true;             // Note: unused dummy return value
}();
 12
Author: emkey08,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-10 11:23:04

Test::StaticTest() jest wywoływany dokładnie raz podczas globalnej inicjalizacji statycznej.

Caller musi dodać tylko jedną linię do funkcji, która ma być ich statycznym konstruktorem.

static_constructor<&Test::StaticTest>::c; wymusza inicjalizację c podczas globalnej inicjalizacji statycznej.

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;
}
 10
Author: bitwise,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-25 22:05:45

Nie ma potrzeby tworzenia funkcji init(), std::vector można tworzyć z zakresu:

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

Zauważ jednak, że statyki typu class powodują problemy w bibliotekach, więc należy ich tam unikać.

C++11 Update

Od C++11 możesz to zrobić zamiast tego:

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

Jest to semantycznie równoważne rozwiązaniu C++98 w oryginalnej odpowiedzi, ale nie można używać literału po prawej stronie, więc nie jest to całkowicie lepsze. Jeśli jednak masz wektor każdy inny rodzaj niż char, wchar_t, char16_t lub char32_t (tablice mogą być zapisywane jako literały łańcuchowe), Wersja C++11 będzie ściśle usuwać kod boilerplate bez wprowadzania innej składni, w porównaniu do wersji C++98.

 9
Author: Marc Mutz - mmutz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-10 21:54:40

Pojęcie konstruktorów statycznych zostało wprowadzone w Javie po zapoznaniu się z problemami w C++. Więc nie mamy bezpośredniego odpowiednika.

Najlepszym rozwiązaniem jest użycie typów POD, które można jawnie zainicjować.
Albo stwórz statyczne elementy konkretnego typu, który ma swój własny konstruktor, który zainicjuje go poprawnie.

//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
Author: Martin York,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-28 22:48:32

Podczas próby kompilacji i Użyj klasy Elsewhere (z odpowiedzi Earwicker ' s answer ) otrzymuję:

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

Wydaje się, że nie jest możliwe zainicjowanie statycznych atrybutów typów innych niż integer bez umieszczania kodu poza definicją klasy (CPP).

Aby skompilować można użyć "statyczna metoda ze statyczną zmienną lokalną wewnątrz" zamiast tego. Coś takiego:

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

I możesz również przekazać argumenty do konstruktora lub zainicjalizować go dzięki określonym wartościom jest bardzo elastyczny, mocny i łatwy do wdrożenia... jedyną rzeczą jest to, że masz statyczną metodę zawierającą statyczną zmienną, a nie statyczny atrybut... syntaxis zmienia się trochę, ale nadal użyteczne. Mam nadzieję, że jest to przydatne dla kogoś,

Hugo González Castro.
 4
Author: Community,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:26:32

Myślę, że prostym rozwiązaniem będzie:

    //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
Author: Shubham,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-21 05:56:55

Rozwiązałem ten sam trik. Musiałem sprecyzować definicję pojedynczego członu statycznego dla Singletona. Ale skomplikuj sprawę bardziej - zdecydowałem, że nie chcę wywoływać ctor of RandClass (), chyba że będę go używał... dlatego nie chciałem inicjować Singletona globalnie w moim kodzie. Również dodałem prosty interfejs w moim przypadku.

Oto ostateczny kod:

Uprościłem kod i używam funkcji rand () oraz jej pojedynczego zalążka inicjującego 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
Author: adspx5,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-20 21:43:38

Oto mój wariant rozwiązania Efraima; różnica polega na tym, że dzięki implicit template instantiation, statyczny konstruktor jest wywoływany tylko wtedy, gdy instancje klasy są tworzone, i że nie jest potrzebna żadna definicja w pliku .cpp (dzięki template instantiation magic).

W pliku .h masz:

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;

W pliku .cpp możesz mieć:

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

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

Zauważ, że {[5] } jest inicjalizowana tylko wtedy, gdy jest tam linia [1], ponieważ wywołuje (i wymaga instancji z) konstruktora, który następnie wymaga instancji _initializer.

 1
Author: Blaisorblade,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-29 11:31:48

Oto inna metoda, w której wektor jest prywatny dla pliku zawierającego implementację za pomocą anonimowej przestrzeni nazw. Jest to przydatne dla rzeczy takich jak tabele wyszukiwania, które są prywatne dla implementacji:

#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
Author: Jim Hunziker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-31 16:20:40

To z pewnością nie musi być tak skomplikowane ,jak obecnie akceptowana odpowiedź (Daniel Earwicker). Klasa jest zbędna. W tym przypadku nie ma potrzeby wojny językowej.

.plik hpp:

vector<char> const & letters();

.plik cpp:

vector<char> const & letters()
{
  static vector<char> v = {'a', 'b', 'c', ...};
  return v;
}
 1
Author: AndyJost,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-05 00:55:35

Statyczne zmienne składowe definiujesz podobnie jak metody składowe.

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
Author: Nick Lewis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-28 22:30:21

Aby zainicjować zmienną statyczną, wystarczy to zrobić wewnątrz pliku źródłowego. Na przykład:

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


//Foo.cpp
int Foo::hello = 1;
 0
Author: Cristián Romo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-28 22:30:44

Jak o stworzeniu szablonu naśladującego zachowanie 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
Author: karmasponge,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-29 08:00:43

Dla prostych przypadków, takich jak tutaj statyczna zmienna zawinięta wewnątrz statycznej funkcji członowej jest prawie tak dobra. Jest to proste i zwykle będzie zoptymalizowane przez kompilatory. Nie rozwiązuje to jednak problemu kolejności inicjalizacji złożonych obiektów.

#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
Author: kriss,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-20 00:39:50

Czy to jest rozwiązanie?

class Foo
{
public:
    size_t count;
    Foo()
    {
        static size_t count = 0;
        this->count = count += 1;
    }
};
 0
Author: BSalita,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-12-07 19:17:38

Statyczny konstruktor może być emulowany za pomocą klasy friend lub klasy zagnieżdżonej, jak poniżej.

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

Wyjście:

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
Author: Jobin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-22 09:18:32

Wow, nie mogę uwierzyć, że nikt nie wspomniał o najbardziej oczywistej odpowiedzi i takiej, która najlepiej naśladuje zachowanie statycznego konstruktora C#, tzn. nie zostanie wywołana, dopóki nie zostanie utworzony pierwszy obiekt tego typu.

std::call_once() jest dostępny w C++11; jeśli nie możesz tego użyć, można to zrobić za pomocą statycznej zmiennej klasy boolean oraz operacji atomowych compare-and-exchange. W konstruktorze sprawdź, czy możesz atomicznie zmienić flagę class-static z false na true, a jeśli tak, możesz uruchomić statyczny-kod budowy.

Aby uzyskać dodatkowe punkty, niech będzie to flaga trójstronna zamiast logicznej, tzn. not run, running I done running. Następnie wszystkie inne instancje tej klasy mogą blokować się, dopóki instancja uruchomiona przez statyczny konstruktor nie zakończy działania (np. wyda memory-fence, a następnie Ustaw stan na "done running"). Twój spin-lock powinien wykonać instrukcję "pause" procesora, podwoić oczekiwanie za każdym razem aż do progu, itp. - dość standardowa technika spinningu.

W brak C++11, to {[13] } powinno zacząć.

Oto jakiś pseudokod, który cię poprowadzi. Umieść to w definicji klasy:

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

I to w Twoim konstruktorze:

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
Author: ulatekh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-01 21:37:12