web-dev-qa-db-de.com

statische Konstruktoren in C++? Ich muss private statische Objekte initialisieren

Ich möchte eine Klasse mit einem privaten statischen Datenelement (einem Vektor, der alle Zeichen a-z enthält) haben. In Java oder C # kann ich einfach einen "statischen Konstruktor" erstellen, der ausgeführt wird, bevor ich Instanzen der Klasse mache und die statischen Datenmitglieder der Klasse einrichtet. Es wird nur einmal ausgeführt (da die Variablen schreibgeschützt sind und nur einmal gesetzt werden müssen). Da es sich um eine Funktion der Klasse handelt, kann es auf seine privaten Mitglieder zugreifen. Ich könnte im Konstruktor Code hinzufügen, der prüft, ob der Vektor initialisiert ist, und ihn initialisieren, wenn dies nicht der Fall ist. Dies führt jedoch viele erforderliche Prüfungen ein und scheint nicht die optimale Lösung für das Problem zu sein.

Der Gedanke kommt mir vor, da die Variablen nur gelesen werden können, können sie nur als public static const bezeichnet werden, sodass ich sie einmal außerhalb der Klasse setzen kann, aber auch dies scheint ein hässlicher Hack zu sein. 

Ist es möglich, private statische Datenelemente in einer Klasse zu haben, wenn ich sie nicht im Instanzkonstruktor initialisieren möchte?

161

Um das Äquivalent eines statischen Konstruktors zu erhalten, müssen Sie eine separate gewöhnliche Klasse schreiben, die die statischen Daten enthält, und dann eine statische Instanz dieser gewöhnlichen Klasse erstellen.

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

};
169

Gut kannst du haben

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

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

Vergiss nicht (in der .cpp) das:

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

Das Programm wird weiterhin ohne die zweite Zeile verbunden, der Initialisierer wird jedoch nicht ausgeführt.

73
EFraim

In der .h-Datei:

class MyClass {
private:
    static int myValue;
};

In der .cpp-Datei:

#include "myclass.h"

int MyClass::myValue = 0;
19
Ant

C++ 11-Lösung

Seit C++ 11 können Sie Lambda-Ausdrücke verwenden, um direkt statische Klassenmitglieder zu initialisieren. Sie müssen keine Problemumgehungen mehr verwenden.

Header-Datei:

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

Quelldatei:

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

Dieses Muster ist ein vollständiger Ersatz für statische Konstruktoren:

  • Sie haben ein genau definiertes Initialisierungsreihenfolge für alle Ihre statischen Member: einfach die gleiche Reihenfolge, die in der Datei source definiert ist.
  • Sie können sowohl Lesen und Schreiben andere (bereits initialisierte!) Statische Member innerhalb des Lambda-Ausdrucks verwenden.
  • Sie können statische Member initialisieren, die abhängig für andere statische Member sind. Sie müssen sie nur in der richtigen Reihenfolge initialisieren.
15
emkey08

Hier ist ein anderer Ansatz, der dem von Daniel Earwicker ähnelt, auch unter Verwendung des Freundesklassenvorschlags von Konrad Rudolph. Hier verwenden wir eine innere private Friend-Utility-Klasse, um die statischen Member Ihrer Hauptklasse zu initialisieren. Zum Beispiel:

Header-Datei:

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

Implementierungsdatei:

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

Dieser Ansatz hat den Vorteil, dass die Initializer-Klasse vollständig vor der Außenwelt verborgen wird, wodurch alles, was in der zu initialisierenden Klasse enthalten ist, beibehalten wird.

14
Douglas Mandell

Test::StaticTest() wird während der globalen statischen Initialisierung genau einmal aufgerufen.

Der Aufrufer muss nur eine Zeile zu der Funktion hinzufügen, die ihr statischer Konstruktor sein soll.

static_constructor<&Test::StaticTest>::c; erzwingt die Initialisierung von c während der globalen statischen Initialisierung.

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
bitwise

Eine init()-Funktion ist nicht erforderlich. std::vector kann aus einem Bereich erstellt werden:

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

Beachten Sie jedoch, dass Statiken des Klassentyps Probleme in Bibliotheken verursachen. Sie sollten daher dort vermieden werden.

C++ 11-Update

Ab C++ 11 können Sie stattdessen Folgendes tun:

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

Es ist semantisch gleichbedeutend mit der C++ 98-Lösung in der ursprünglichen Antwort. Sie können jedoch kein String-Literal auf der rechten Seite verwenden, sodass es nicht vollständig überlegen ist. Wenn Sie jedoch einen Vektor eines anderen Typs als char, wchar_t, char16_t oder char32_t (Arrays, die als String-Literale geschrieben werden können) haben, entfernt die C++ 11-Version den Boilerplate-Code strikt, ohne dass eine andere Syntax verwendet wird C++ 98-Version.

9

Das Konzept der statischen Konstruktoren wurde in Java eingeführt, nachdem sie aus den Problemen in C++ gelernt hatten. Wir haben also keine direkte Entsprechung.

Die beste Lösung ist die Verwendung von POD-Typen, die explizit initialisiert werden können.
Oder machen Sie Ihre statischen Member zu einem bestimmten Typ, der über einen eigenen Konstruktor verfügt, der ihn korrekt initialisiert.

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

Beim Versuch zu kompilieren und use -Klasse Elsewhere (aus Earwickers Antwort ) erhalte ich:

error LNK2001: unresolved external symbol "private: static class StaticStuff Elsewhere::staticStuff" ([email protected]@@[email protected]@A)

Es ist nicht möglich, statische Attribute von nicht ganzzahligen Typen zu initialisieren, ohne Code außerhalb der Klassendefinition (CPP) zu verwenden.

Um dies zu kompilieren, können Sie stattdessen "eine statische Methode mit einer statischen lokalen Variablen" verwenden. Etwas wie das:

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

Sie können auch Argumente an den Konstruktor übergeben oder ihn mit bestimmten Werten initialisieren. Er ist sehr flexibel, leistungsstark und einfach zu implementieren. Die einzige Sache ist, dass Sie eine statische Methode haben, die eine statische Variable enthält, kein statisches Attribut ... Syntax ändert sich ein wenig, aber immer noch nützlich. Hoffe das ist nützlich für jemanden,

Hugo González Castro.

4
user260858

Ich denke, eine einfache Lösung dafür wird sein:

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

Gerade den gleichen Trick gelöst. Ich musste die Definition eines einzelnen statischen Members für Singleton angeben .. Aber etwas komplizierter machen - ich habe beschlossen, ctor von RandClass () nicht zu nennen, es sei denn, ich benutze es ... deshalb benutze ich es Ich wollte Singleton nicht global in meinem Code initialisieren. Ich habe auch eine einfache Schnittstelle in meinem Fall hinzugefügt.

Hier ist der endgültige Code:

Ich habe den Code vereinfacht und verwende die Rand () - Funktion und deren Single Seeds initialzer 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 bietet

__attribute__((constructor))

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

Kennzeichnen Sie eine statische Methode mit diesem Attribut. Sie wird beim Laden des Moduls vor main () ausgeführt.

1
jws

Hier ist eine andere Methode, bei der der Vektor für die Datei, die die Implementierung enthält, mit einem anonymen Namespace privat ist. Dies ist nützlich für Dinge wie Nachschlagetabellen, die für die Implementierung privat sind:

#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

Es muss sicherlich nicht so kompliziert sein wie die derzeit akzeptierte Antwort (von Daniel Earwicker). Die Klasse ist überflüssig. In diesem Fall ist kein Sprachkrieg erforderlich.

.hpp-Datei:

vector<char> const & letters();

.cpp-Datei:

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

Hier ist meine Variante der EFraim-Lösung. Der Unterschied ist, dass der statische Konstruktor dank impliziter Template-Instantiierung nur aufgerufen wird, wenn Instanzen der Klasse erstellt werden und dass in der .cpp-Datei keine Definition erforderlich ist (dank der Instantiator-Instanziierung).

In der .h-Datei haben Sie:

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;

In der .cpp-Datei können Sie Folgendes haben:

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

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

Beachten Sie, dass MyClass::a nur initialisiert wird, wenn Zeile [1] vorhanden ist, da dies den Konstruktor aufruft (und eine Instantiierung des Konstruktors erfordert), der dann die Instantiierung von _initializer erfordert.

1
Blaisorblade

Wow, ich kann nicht glauben, dass niemand die naheliegendste Antwort erwähnt hat und eine, die dem statischen Konstruktorverhalten von C # am nächsten kommt, d. H. Es wird nicht aufgerufen, bis das erste Objekt dieses Typs erstellt wird.

std::call_once() ist in C++ 11 verfügbar. Wenn Sie das nicht verwenden können, können Sie dies mit einer statischen booleschen Klassenvariablen und einer atomaren Compare-and-Exchange-Operation tun. Prüfen Sie in Ihrem Konstruktor, ob Sie das Klassenstatik-Flag atomar von false in true ändern können, und wenn dies der Fall ist, können Sie den statischen Konstruktionscode ausführen.

Für zusätzliches Guthaben machen Sie es zu einem 3-Wege-Flag anstelle eines Booleschen Zeichens, d. H. Nicht ausgeführt, ausgeführt und abgeschlossen. Dann können alle anderen Instanzen dieser Klasse spinnen, bis die Instanz, die den statischen Konstruktor ausführt, beendet ist (d. H. Einen Speicherzaun ausgeben und dann den Status auf "Fertig ausgeführt" setzen). Ihr Spin-Lock sollte die "Pause" -Anweisung des Prozessors ausführen, die Wartezeit jedes Mal bis zu einem Schwellenwert verdoppeln usw. - ziemlich standardmäßige Spin-Lock-Technik.

In Abwesenheit von C++ 11 sollten Sie mit dieses beginnen.

Hier ist ein Pseudocode, um Sie zu führen. Fügen Sie dies in Ihre Klassendefinition ein:

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

Und das in Ihrem Konstruktor:

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

Sie definieren statische Membervariablen ähnlich wie Sie Membermethoden definieren.

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

Um eine statische Variable zu initialisieren, tun Sie dies einfach in einer Quelldatei. Zum Beispiel:

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


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

Wie wäre es mit einer Vorlage, die das Verhalten von C # nachahmt?.

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

Ein statischer Konstruktor kann wie folgt emuliert werden, indem eine Friend-Klasse oder eine verschachtelte Klasse verwendet wird.

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

Ausgabe:

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

Ist das eine Lösung?

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

In einfachen Fällen wie hier ist eine statische Variable, die in eine statische Elementfunktion eingeschlossen ist, fast genauso gut. Es ist einfach und wird normalerweise von Compilern optimiert. Dies löst jedoch nicht das Problem der Initialisierungsreihenfolge für komplexe Objekte.

#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