it-swarm.com.de

Wie initialisiere ich private statische Member in C ++?

Wie kann ein privates statisches Datenelement in C++ am besten initialisiert werden? Ich habe dies in meiner Header-Datei versucht, aber es gibt mir seltsame Linker-Fehler:

class foo
{
    private:
        static int i;
};

int foo::i = 0;

Ich vermute, das liegt daran, dass ich kein privates Mitglied außerhalb der Klasse initialisieren kann. Also, wie geht das am besten?

476
Jason Baker

Die Klassendeklaration sollte sich in der Headerdatei befinden (oder in der Quelldatei, wenn sie nicht gemeinsam genutzt wird).
Datei: foo.h

class foo
{
    private:
        static int i;
};

Die Initialisierung sollte sich jedoch in der Quelldatei befinden.
Datei: foo.cpp

int foo::i = 0;

Befindet sich die Initialisierung in der Header-Datei, enthält jede Datei, die die Header-Datei enthält, eine Definition des statischen Members. Während der Verknüpfungsphase treten daher Linker-Fehler auf, da der Code zum Initialisieren der Variablen in mehreren Quelldateien definiert wird.

Hinweis: Matt Curtis: weist darauf hin, dass C++ die Vereinfachung des Obigen ermöglicht, wenn die statische Elementvariable vom Typ const int ist (z. B. int, bool, char). Anschließend können Sie die Member-Variable direkt in der Klassendeklaration in der Header-Datei deklarieren und initialisieren:

class foo
{
    private:
        static int const i = 42;
};
511
Martin York

Für ein Variable:

foo.h:

class foo
{
private:
    static int i;
};

foo.cpp:

int foo::i = 0;

Dies liegt daran, dass Ihr Programm nur eine Instanz von foo::i enthalten kann. Es ist eine Art Äquivalent zu extern int i in einer Header-Datei und int i in einer Quelldatei.

Für eine Konstante können Sie den Wert direkt in die Klassendeklaration einfügen:

class foo
{
private:
    static int i;
    const static int a = 42;
};
85
Matt Curtis

Für zukünftige Betrachter dieser Frage möchte ich darauf hinweisen, dass Sie vermeiden sollten, was monkey0506 schlägt vor .

Header-Dateien sind für Deklarationen.

Header-Dateien werden einmal für jede .cpp-Datei kompiliert, die sie direkt oder indirekt #includes enthält, und Code außerhalb einer Funktion wird bei der Programminitialisierung vor main() ausgeführt.

Durch das Einfügen von: foo::i = VALUE; in den Header wird foo:i der Wert VALUE (was auch immer das ist) für jede .cpp-Datei zugewiesen, und diese Zuweisungen erfolgen in einer unbestimmten Reihenfolge (bestimmt durch den Linker) vor main() es läuft.

Was ist, wenn wir #define VALUE als andere Nummer in einer unserer .cpp-Dateien angeben? Es wird gut kompiliert und wir werden keine Möglichkeit haben zu wissen, welcher gewinnt, bis wir das Programm ausführen.

Fügen Sie niemals ausgeführten Code in einen Header ein, aus dem gleichen Grund, aus dem Sie niemals eine Datei #include.cpp __en.

include guards (ich bin damit einverstanden, dass Sie immer etwas anderes verwenden sollten) schützen Sie vor etwas anderem: Der gleiche Header ist indirekt #included mehrmals, während eine einzelne .cpp-Datei kompiliert wird

26
Joshua Clayton

Seit C++ 17 können statische Member im Header mit dem Inline-Schlüsselwort definiert werden.

http://en.cppreference.com/w/cpp/language/static

Ein statisches Datenelement kann als Inline deklariert werden. Ein statisches Inline-Datenelement kann in der Klassendefinition definiert werden und einen Standardelementinitialisierer angeben. Es ist keine Definition außerhalb der Klasse erforderlich:

struct X
{
    inline static int n = 1;
};
23
Die in Sente

Mit einem Microsoft-Compiler [1] können statische Variablen, die nicht int -artig sind, auch in einer Header-Datei definiert werden, jedoch außerhalb der Klassendeklaration, wobei die Microsoft-spezifische __declspec(selectany) verwendet wird.

class A
{
    static B b;
}

__declspec(selectany) A::b;

Beachten Sie, dass ich nicht sage, dass dies gut ist, ich sage nur, dass es getan werden kann.

[1] Heutzutage unterstützen mehr Compiler als MSC __declspec(selectany) - zumindest gcc und clang. Vielleicht noch mehr.

19
Johann Gerell
int foo::i = 0; 

Ist die richtige Syntax zum Initialisieren der Variablen, muss sich jedoch in der Quelldatei (.cpp) und nicht im Header befinden.

Da es sich um eine statische Variable handelt, muss der Compiler nur eine Kopie davon erstellen. Sie müssen eine Zeile "int foo: i" in Ihrem Code haben, um dem Compiler mitzuteilen, wo er abgelegt werden soll, andernfalls wird ein Linkfehler angezeigt. Befindet sich dies in einer Kopfzeile, erhalten Sie in jeder Datei, die die Kopfzeile enthält, eine Kopie. Daher erhalten Sie vom Linker mehrfach definierte Symbolfehler.

16
David Dibben

Wenn Sie einen zusammengesetzten Typ (z. B. einen String) initialisieren möchten, können Sie Folgendes tun:

class SomeClass {
  static std::list<string> _list;

  public:
    static const std::list<string>& getList() {
      struct Initializer {
         Initializer() {
           // Here you may want to put mutex
           _list.Push_back("FIRST");
           _list.Push_back("SECOND");
           ....
         }
      }
      static Initializer ListInitializationGuard;
      return _list;
    }
};

Da die ListInitializationGuard eine statische Variable innerhalb der SomeClass::getList() -Methode ist, wird sie nur einmal erstellt, was bedeutet, dass der Konstruktor einmal aufgerufen wird. Dies wird die initialize _list Variable auf den von Ihnen benötigten Wert setzen. Jeder nachfolgende Aufruf von getList gibt einfach das bereits initialisierte Objekt _list zurück.

Natürlich müssen Sie immer auf das Objekt _list zugreifen, indem Sie die Methode getList() aufrufen.

12

Ich habe nicht genug Repräsentanten hier, um dies als Kommentar hinzuzufügen, aber IMO ist es ein guter Stil, Ihre Überschriften mit # include guards zu schreiben, was, wie Paranaix vor einigen Stunden bemerkte, ein Vielfaches verhindern würde -Definitionsfehler. Sofern Sie nicht bereits eine separate CPP-Datei verwenden, ist es nicht erforderlich, nur eine zu verwenden, um statische nichtintegrale Elemente zu initialisieren.

#ifndef FOO_H
#define FOO_H
#include "bar.h"

class foo
{
private:
    static bar i;
};

bar foo::i = VALUE;
#endif

Ich sehe keine Notwendigkeit, eine separate CPP-Datei dafür zu verwenden. Sicher können Sie, aber es gibt keinen technischen Grund, warum Sie müssen sollten.

11
monkey0506

Statisches Konstruktormuster, das für mehrere Objekte funktioniert

Eine Redewendung wurde unter folgender Adresse vorgeschlagen: https://stackoverflow.com/a/27088552/895245 Hier jedoch eine übersichtlichere Version, für die keine neue Methode pro Mitglied erstellt werden muss, und ein ausführbares Beispiel:

#include <cassert>
#include <vector>

// Normally on the .hpp file.
class MyClass {
public:
    static std::vector<int> v, v2;
    static struct _StaticConstructor {
        _StaticConstructor() {
            v.Push_back(1);
            v.Push_back(2);
            v2.Push_back(3);
            v2.Push_back(4);
        }
    } _staticConstructor;
};

// Normally on the .cpp file.
std::vector<int> MyClass::v;
std::vector<int> MyClass::v2;
// Must come after every static member.
MyClass::_StaticConstructor MyClass::_staticConstructor;

int main() {
    assert(MyClass::v[0] == 1);
    assert(MyClass::v[1] == 2);
    assert(MyClass::v2[0] == 3);
    assert(MyClass::v2[1] == 4);
}

GitHub upstream .

Siehe auch: statische Konstruktoren in C++? Ich muss private statische Objekte initialisieren

Getestet mit g++ -std=c++11 -Wall -Wextra, GCC 7.3, Ubuntu 18.04.

Sie können die Zuordnung auch in die Header-Datei aufnehmen, wenn Sie Header-Guards verwenden. Ich habe diese Technik für eine C++ - Bibliothek verwendet, die ich erstellt habe. Eine andere Möglichkeit, das gleiche Ergebnis zu erzielen, ist die Verwendung statischer Methoden. Zum Beispiel...

class Foo
   {
   public:
     int GetMyStatic() const
     {
       return *MyStatic();
     }

   private:
     static int* MyStatic()
     {
       static int mStatic = 0;
       return &mStatic;
     }
   }

Der obige Code hat den "Bonus", dass keine CPP-/Quelldatei erforderlich ist. Wieder eine Methode, die ich für meine C++ - Bibliotheken verwende.

5
user2225284

Ich folge der Idee von Karl. Ich mag es und benutze es jetzt auch. Ich habe die Schreibweise ein wenig geändert und einige Funktionen hinzugefügt

#include <stdio.h>

class Foo
{
   public:

     int   GetMyStaticValue () const {  return MyStatic();  }
     int & GetMyStaticVar ()         {  return MyStatic();  }
     static bool isMyStatic (int & num) {  return & num == & MyStatic(); }

   private:

      static int & MyStatic ()
      {
         static int mStatic = 7;
         return mStatic;
      }
};

int main (int, char **)
{
   Foo obj;

   printf ("mystatic value %d\n", obj.GetMyStaticValue());
   obj.GetMyStaticVar () = 3;
   printf ("mystatic value %d\n", obj.GetMyStaticValue());

   int valMyS = obj.GetMyStaticVar ();
   int & iPtr1 = obj.GetMyStaticVar ();
   int & iPtr2 = valMyS;

   printf ("is my static %d %d\n", Foo::isMyStatic(iPtr1), Foo::isMyStatic(iPtr2));
}

diese Ausgänge

mystatic value 7
mystatic value 3
is my static 1 0
4

Funktioniert auch in der Datei privateStatic.cpp:

#include <iostream>

using namespace std;

class A
{
private:
  static int v;
};

int A::v = 10; // possible initializing

int main()
{
A a;
//cout << A::v << endl; // no access because of private scope
return 0;
}

// g++ privateStatic.cpp -o privateStatic && ./privateStatic
3
andrew

Was ist mit einer set_default() -Methode?

class foo
{
    public:
        static void set_default(int);
    private:
        static int i;
};

void foo::set_default(int x) {
    i = x;
}

Wir müssten nur die Methode set_default(int x) verwenden und unsere Variable static würde initialisiert.

Dies würde nicht im Widerspruch zu den übrigen Kommentaren stehen. Tatsächlich folgt es demselben Prinzip wie die Initialisierung der Variablen in einem globalen Bereich. Mit dieser Methode machen wir es jedoch explizit (und leicht verständlich), anstatt die Definition zu haben der dort hängenden Variable.

3

Eine "alte" Art, Konstanten zu definieren, besteht darin, sie durch ein enum zu ersetzen:

class foo
{
    private:
        enum {i = 0}; // default type = int
        enum: int64_t {HUGE = 1000000000000}; // may specify another type
};

Auf diese Weise muss keine Definition angegeben werden, und es wird vermieden, die Konstante lvalue festzulegen, wodurch Sie einige Kopfschmerzen sparen können, z. wenn Sie versehentlich ODR-Verwendung es.

2
anatolyg

Das Linker-Problem, auf das Sie gestoßen sind, wird wahrscheinlich verursacht durch:

  • Bereitstellung der Definition von Klassen und statischen Elementen in der Header-Datei,
  • Einschließen dieses Headers in zwei oder mehr Quelldateien.

Dies ist ein häufiges Problem für diejenigen, die mit C++ beginnen. Statische Klassenmitglieder müssen in einer einzelnen Übersetzungseinheit initialisiert werden, d. H. In einer einzelnen Quelldatei.

Leider muss das statische Klassenmitglied außerhalb des Klassenkörpers initialisiert werden. Dies erschwert das Schreiben von Nur-Header-Code, weshalb ich einen ganz anderen Ansatz verwende. Sie können Ihr statisches Objekt durch statische oder nicht statische Klassenfunktionen bereitstellen, zum Beispiel:

class Foo
{
    // int& getObjectInstance() const {
    static int& getObjectInstance() {
        static int object;
        return object;
    }

    void func() {
        int &object = getValueInstance();
        object += 5;
    }
};
2
no one special

Ich wollte nur etwas Seltsames erwähnen, als ich das erste Mal darauf stieß.

Ich musste ein privates statisches Datenelement in einer Vorlagenklasse initialisieren.

in der .h- oder .hpp-Datei sieht es ungefähr so ​​aus, wenn ein statisches Datenelement einer Vorlagenklasse initialisiert wird:

template<typename T>
Type ClassName<T>::dataMemberName = initialValue;
1
Tyler Heers

Hat dies Ihren Zweck erfüllt?

//header file

struct MyStruct {
public:
    const std::unordered_map<std::string, uint32_t> str_to_int{
        { "a", 1 },
        { "b", 2 },
        ...
        { "z", 26 }
    };
    const std::unordered_map<int , std::string> int_to_str{
        { 1, "a" },
        { 2, "b" },
        ...
        { 26, "z" }
    };
    std::string some_string = "justanotherstring";  
    uint32_t some_int = 42;

    static MyStruct & Singleton() {
        static MyStruct instance;
        return instance;
    }
private:
    MyStruct() {};
};

//Usage in cpp file
int main(){
    std::cout<<MyStruct::Singleton().some_string<<std::endl;
    std::cout<<MyStruct::Singleton().some_int<<std::endl;
    return 0;
}
0
David Nogueira