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?
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;
};
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;
};
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 #include
d mehrmals, während eine einzelne .cpp
-Datei kompiliert wird
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;
};
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.
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.
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.
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.
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);
}
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.
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
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
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.
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.
Das Linker-Problem, auf das Sie gestoßen sind, wird wahrscheinlich verursacht durch:
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;
}
};
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;
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;
}