Wie initialisiere ich private statische Mitglieder in C ++?

519

Was ist der beste Weg, um ein privates statisches Datenelement in C ++ zu initialisieren? 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 ein privates Mitglied von außerhalb der Klasse nicht initialisieren kann. Was ist der beste Weg, dies zu tun?

Jason Baker
quelle
2
Hallo Jason. Ich habe keinen Kommentar zur Standardinitialisierung statischer Elemente (insbesondere integraler Elemente) gefunden. Tatsächlich müssen Sie int foo :: i schreiben, damit der Linker es finden kann, aber es wird automatisch mit 0 initialisiert! Diese Zeile würde ausreichen: int foo :: i; (Dies gilt für alle Objekte, die im statischen Speicher gespeichert sind. Der Linker ist für die Initialisierung der statischen Objekte verantwortlich.)
Nico
1
Die folgenden Antworten gelten nicht für eine Vorlagenklasse. Sie sagen: Die Initialisierung muss in die Quelldatei gehen. Für eine Vorlagenklasse ist dies weder möglich noch notwendig.
Joachim W
7
C ++ 17 ermöglicht die Inline-Initialisierung statischer Datenelemente (auch für nicht ganzzahlige Typen) : inline static int x[] = {1, 2, 3};. Siehe en.cppreference.com/w/cpp/language/static#Static_data_members
Vladimir Reshetnikov

Antworten:

556

Die Klassendeklaration sollte sich in der Header-Datei befinden (oder in der Quelldatei, wenn sie nicht freigegeben ist).
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;

Wenn sich die Initialisierung in der Header-Datei befindet, enthält jede Datei, die die Header-Datei enthält, eine Definition des statischen Elements. Während der Link-Phase werden daher Linker-Fehler angezeigt, da der Code zum Initialisieren der Variablen in mehreren Quelldateien definiert wird. Die Initialisierung von static int imuss außerhalb einer Funktion erfolgen.

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

class foo
{
    private:
        static int const i = 42;
};
Martin York
quelle
4
Ja. Ich gehe jedoch davon aus, dass die Frage vereinfacht wurde. Technisch gesehen können sich die Deklaration und Definition alle in einer einzigen Quelldatei befinden. Dies schränkt dann jedoch die Verwendung von Klassen durch andere Klassen ein.
Martin York
11
Eigentlich nicht nur POD, es muss auch ein Int-Typ sein (Int, Short, Bool, Char ...)
Matt Curtis
9
Beachten Sie, dass dies nicht nur eine Frage der Initialisierung des Werts ist: Konstante Integraltypen, die so definiert sind, können von der Implementierung in Kompilierungszeitkonstanten umgewandelt werden. Dies ist nicht immer das, was Sie wollen, da es die binäre Abhängigkeit erhöht: Client-Code muss neu kompiliert werden, wenn sich der Wert ändert.
Steve Jessop
5
@Martin: Zusätzlich zur Korrektur s / POD / Integraltyp / muss, falls die Adresse jemals genommen wird, auch eine Definition vorhanden sein. So seltsam es auch klingen mag, die Deklaration mit dem Initialisierer in der Klassendefinition ist keine Definition. Das Konstanten-Idiom mit Vorlagen bietet eine Problemumgehung für die Fälle, in denen Sie die Definition in einer Header-Datei benötigen. Eine andere und einfachere Problemumgehung ist eine Funktion, die den Wert einer lokalen statischen Konstante erzeugt. Prost & hth.,
Prost und hth. - Alf
3
Sie können eine Klarstellung hinzufügen, dass int foo :: i = 0; sollte sich nicht in einer Funktion befinden (einschließlich der Hauptfunktion). Ich hatte es am Anfang meiner Hauptfunktion und es gefällt mir nicht.
QWERTY9967
89

Für eine Variable :

foo.h:

class foo
{
private:
    static int i;
};

foo.cpp:

int foo::i = 0;

Dies liegt daran, dass foo::iIhr Programm nur eine Instanz enthalten kann. Dies entspricht extern int iin etwa einer Header-Datei und int ieiner 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;
};
Matt Curtis
quelle
2
Dies ist ein gültiger Punkt. Ich werde dies auch meine Erklärung hinzufügen. Es ist jedoch zu beachten, dass dies nur für POD-Typen funktioniert.
Martin York
Seit wann kann C ++ nur gut mit Deklaration in der Klasse und ohne Definition für integrale Typen umgehen. Seit C ++ 98 selbst oder C ++ 03 oder wann? Bitte teilen Sie authentische Links bitte. Der C ++ - Standardtext ist nicht mit den Compilern synchronisiert. Sie erwähnen, dass das Mitglied noch definiert werden soll, wenn es verwendet wird. Also, ich brauche das C ++ Standard Zitat nicht
smRaj
1
Ich frage mich, warum privateVariablen hier außerhalb von Class initialisiert werden können. Kann dies auch für nicht statische Variablen durchgeführt werden?
Krishna Oza
Hast du die Erklärung gefunden? @Krishna_Oza
nn0p
@ nn0p noch nicht, aber die Initialisierung nicht statischer privater Variablen außerhalb Classmacht in Cpp keinen Sinn.
Krishna Oza
41

Seit C ++ 17 können statische Elemente im Header mit dem Schlüsselwort inline 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;
};
Stirb in Sente
quelle
1
Dies ist seit C ++ 17 möglich, das derzeit zum neuen Standard wird.
Grebu
31

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

Header-Dateien sind für Deklarationen.

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

Durch Einfügen von: foo::i = VALUE;in den Header foo:iwird der Wert VALUE(was auch immer das ist) für jede .cppDatei zugewiesen , und diese Zuweisungen erfolgen in einer unbestimmten Reihenfolge (vom Linker festgelegt), bevor sie main()ausgeführt werden.

Was ist, wenn wir #define VALUEin einer unserer .cppDateien eine andere Nummer haben ? Es wird gut kompiliert und wir werden keine Möglichkeit haben zu wissen, welches 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 #includeeine .cppDatei haben.

Include-Wachen (die Sie meiner Meinung nach immer verwenden sollten) schützen Sie vor etwas anderem: Der gleiche Header wird #includebeim Kompilieren einer einzelnen .cppDatei indirekt mehrmals d

Joshua Clayton
quelle
2
Da haben Sie natürlich Recht, außer im Fall einer Klassenvorlage (nach der nicht gefragt wird, aber ich habe zufällig viel zu tun). Wenn die Klasse vollständig definiert ist und keine Klassenvorlage, fügen Sie diese statischen Elemente in eine separate CPP-Datei ein. Bei Klassenvorlagen muss sich die Definition jedoch in derselben Übersetzungseinheit befinden (z. B. in der Header-Datei).
monkey0506
@ monkey_05_06: Das scheint nur ein Argument zu sein, um statische Elemente im Vorlagencode zu vermeiden: Sie haben bereits ein statisches Element für jede Instanziierung der Klasse. Das Problem wird verschlimmert, indem möglicherweise der Header in mehrere CPP-Dateien kompiliert wird. Sie könnten eine Reihe widersprüchlicher Definitionen erhalten.
Joshua Clayton
publib.boulder.ibm.com/infocenter/macxhelp/v6v81/… Dieser Link zeigt die Instanziierung statischer Vorlagenelemente in der Hauptfunktion, die sauberer ist, wenn auch etwas belastend.
Joshua Clayton
1
Ihr Argument ist wirklich sehr groß. Erstens können Sie VALUE nicht definieren, da der Makroname kein gültiger Bezeichner sein muss. Und selbst wenn Sie könnten - wer würde das tun? Header-Dateien sind zur Deklaration -? Komm schon. Der einzige Fall, in dem du vermeiden solltest, Werte in den Header zu setzen, ist der Kampf gegen odr-used. Das Einfügen des Werts in den Header kann zu einer unnötigen Neukompilierung führen, wenn Sie den Wert ändern müssen.
Aleksander Fular
20

Mit einem Microsoft-Compiler [1] können statische Variablen, die nicht intähnlich sind, auch in einer Header-Datei definiert werden, jedoch außerhalb der Klassendeklaration unter Verwendung der Microsoft-spezifischen __declspec(selectany).

class A
{
    static B b;
}

__declspec(selectany) A::b;

Beachten Sie, dass ich nicht sage, dass dies gut ist, sondern nur, dass dies möglich ist.

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

Johann Gerell
quelle
17
int foo::i = 0; 

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

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 sie ablegen soll. Andernfalls wird ein Linkfehler angezeigt. Wenn sich das in einem Header befindet, erhalten Sie in jeder Datei, die den Header enthält, eine Kopie. Erhalten Sie also mehrfach definierte Symbolfehler vom Linker.

David Dibben
quelle
12

Ich habe hier nicht genug Repräsentanten, um dies als Kommentar hinzuzufügen, aber IMO ist es ein guter Stil, Ihre Header trotzdem mit # include-Wachen zu schreiben , was, wie Paranaix vor einigen Stunden feststellte, einen Fehler mit mehreren Definitionen verhindern würde. Sofern Sie nicht bereits eine separate CPP-Datei verwenden, ist es nicht erforderlich, nur eine zu verwenden, um statische nicht integrale 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, dafür eine separate CPP-Datei zu verwenden. Sicher können Sie, aber es gibt keinen technischen Grund, warum Sie müssen sollten.

monkey0506
quelle
21
#include-Schutzvorrichtungen verhindern lediglich mehrere Definitionen pro Übersetzungseinheit.
Paul Fultz II
3
in Bezug auf guten Stil: Sie sollten Kommentar zum Schlussende hinzufügen:#endif // FOO_H
Riga
9
Dies funktioniert nur, wenn Sie nur eine Kompilierungseinheit haben, die foo.h enthält. Wenn zwei oder mehr cpps foo.h enthalten, was eine typische Situation ist, würde jeder cpp dieselbe statische Variable deklarieren, sodass sich der Linker mit einer Mehrfachdefinition von "foo :: i" beschweren würde, es sei denn, Sie verwenden eine Paketkompilierung mit den Dateien (compile) nur eine Datei, die alle cpps enthält). Obwohl die Paketkompilierung großartig ist, besteht die Lösung des Problems darin, (int foo :: i = 0;) in einem cpp zu deklarieren!
Alejadro Xalabarder
1
Oder verwenden Sie einfach#pragma once
Tambre
12

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 es sich bei der Methode ListInitializationGuardum eine statische Variable innerhalb der SomeClass::getList()Methode handelt, wird sie nur einmal erstellt, was bedeutet, dass der Konstruktor einmal aufgerufen wird. Dies initialize _listvariiert je nach dem Wert, den Sie benötigen. Bei jedem nachfolgenden Aufruf von getListwird einfach das bereits initialisierte _listObjekt zurückgegeben.

Natürlich muss man darauf zugreifen _list immer durch Aufrufen der getList()Methode Objekt .

Kris Kwiatkowski
quelle
1
Hier ist eine Version dieser Redewendung, für die keine Methode pro Mitgliedsobjekt erstellt werden muss: stackoverflow.com/a/48337288/895245
Ciro Santilli 法轮功 冠状 病 六四 事件 19
9

Statisches C ++ 11-Konstruktormuster, das für mehrere Objekte funktioniert

Eine Redewendung wurde vorgeschlagen unter: https://stackoverflow.com/a/27088552/895245, aber hier ist eine sauberere Version, bei der keine neue Methode pro Mitglied erstellt werden muss.

main.cpp

#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 stromaufwärts .

Kompilieren und ausführen:

g++ -ggdb3 -O0 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp
./main.out

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

Getestet unter Ubuntu 19.04.

C ++ 17 Inline-Variable

Erwähnt unter: https://stackoverflow.com/a/45062055/895245, aber hier ist ein ausführbares Beispiel für mehrere Dateien, um es noch klarer zu machen: Wie funktionieren Inline-Variablen?

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
quelle
5

Sie können die Zuweisung auch in die Header-Datei aufnehmen, wenn Sie Header-Guards verwenden. Ich habe diese Technik für eine von mir erstellte C ++ - Bibliothek verwendet. 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.


quelle
4

Ich folge der Idee von Karl. Ich mag es und jetzt benutze ich es auch. Ich habe die Notation 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));
}

dies gibt aus

mystatic value 7
mystatic value 3
is my static 1 0
Alejadro Xalabarder
quelle
3

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
Andrew
quelle
3

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 set_default(int x)Methode verwenden und unsere staticVariable würde initialisiert.

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

Arturo Ruiz Mañas
quelle
3

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

  • Bereitstellung sowohl der Klassen- als auch der statischen Elementdefinition in der Headerdatei,
  • Einfügen dieses Headers in zwei oder mehr Quelldateien.

Dies ist ein häufiges Problem für diejenigen, die mit C ++ beginnen. Das statische Klassenmitglied muss in einer einzelnen Übersetzungseinheit, dh in einer einzelnen Quelldatei, initialisiert werden.

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

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

    void func() {
        int &object = getValueInstance();
        object += 5;
    }
};
niemand besonderes
quelle
1
Ich bin immer noch ein komplettes n00b, was C ++ angeht, aber das sieht für mich brillant aus, vielen Dank! Ich bekomme kostenlos ein perfektes Lebenszyklus-Management des Singleton-Objekts.
Rafael Kitover
2

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

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, dass der konstante Wert l festgelegt wird , wodurch Sie einige Kopfschmerzen sparen können, z. B. wenn Sie ihn versehentlich mit ODR verwenden .

Anatolyg
quelle
1

Ich wollte nur etwas erwähnen, das mir etwas fremd ist, als ich das zum ersten Mal sah.

Ich musste ein privates statisches Datenelement in einer Vorlagenklasse initialisieren.

In der Datei .h oder .hpp sieht es ungefähr so ​​aus, als würde ein statisches Datenelement einer Vorlagenklasse initialisiert:

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

Dient dies Ihrem Zweck?

//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;
}
David Nogueira
quelle