Quel est le meilleur moyen d'initialiser un membre de données statique et privé en C++? J'ai essayé ceci dans mon fichier d'en-tête, mais cela me donne des erreurs bizarres de l'éditeur de liens:
class foo
{
private:
static int i;
};
int foo::i = 0;
Je suppose que c'est parce que je ne peux pas initialiser un membre privé de l'extérieur de la classe. Alors, quelle est la meilleure façon de faire cela?
La déclaration de classe doit figurer dans le fichier d’en-tête (ou dans le fichier source si elle n’est pas partagée).
Fichier: foo.h
class foo
{
private:
static int i;
};
Mais l'initialisation doit être dans le fichier source.
Fichier: foo.cpp
int foo::i = 0;
Si l'initialisation est dans le fichier d'en-tête, chaque fichier contenant le fichier d'en-tête aura une définition du membre statique. Ainsi, pendant la phase de liaison, vous obtiendrez des erreurs de l'éditeur de liens car le code pour initialiser la variable sera défini dans plusieurs fichiers source.
Remarque: Matt Curtis: fait remarquer que C++ permet la simplification de ce qui précède si la variable de membre statique est de type const int (par exemple, int
, bool
, char
). Vous pouvez ensuite déclarer et initialiser la variable membre directement à l'intérieur de la déclaration de classe dans le fichier d'en-tête:
class foo
{
private:
static int const i = 42;
};
Pour un variable:
foo.h:
class foo
{
private:
static int i;
};
foo.cpp:
int foo::i = 0;
En effet, il ne peut y avoir qu'une seule instance de foo::i
dans votre programme. C'est en quelque sorte l'équivalent de extern int i
dans un fichier d'en-tête et de int i
dans un fichier source.
Pour un constante, vous pouvez mettre la valeur directement dans la déclaration de classe:
class foo
{
private:
static int i;
const static int a = 42;
};
Pour les futurs téléspectateurs de cette question, je tiens à souligner que vous devez éviter ce que monkey0506 suggère .
Les fichiers d'en-tête sont pour les déclarations.
Les fichiers d'en-tête sont compilés une fois pour chaque fichier .cpp
qui les _ directement ou indirectement #includes
, et le code en dehors de toute fonction est exécuté à l'initialisation du programme, avant main()
.
En mettant: foo::i = VALUE;
dans l'en-tête, foo:i
se verra attribuer la valeur VALUE
(quelle que soit cette propriété) pour chaque fichier .cpp
, et ces affectations se dérouleront dans un ordre indéterminé. (déterminé par l'éditeur de liens) avant que main()
ne soit exécuté.
Que faire si nous #define VALUE
pour être un numéro différent dans l’un de nos fichiers .cpp
? La compilation sera parfaite et nous n’aurons aucun moyen de savoir lequel gagne jusqu’à ce que nous exécutions le programme.
Ne mettez jamais de code exécuté dans un en-tête pour la même raison que vous n'avez jamais #include
un fichier .cpp
.
include gardes (que je suis d’accord que vous devriez toujours utiliser) vous protègent de quelque chose de différent: le même en-tête étant indirectement #include
d plusieurs fois lors de la compilation d’un seul fichier .cpp
Depuis C++ 17, les membres statiques peuvent être définis dans l'en-tête avec le mot clé inline.
http://en.cppreference.com/w/cpp/language/static
"Un membre de données statique peut être déclaré en ligne. Un membre de données statique en ligne peut être défini dans la définition de classe et peut spécifier un initialiseur de membre par défaut. Il n'a pas besoin de définition en dehors de la classe:"
struct X
{
inline static int n = 1;
};
Avec un compilateur Microsoft [1], les variables statiques qui ne sont pas de type int
peuvent également être définies dans un fichier d’en-tête, mais en dehors de la déclaration de classe, à l’aide de la __declspec(selectany)
spécifique à Microsoft.
class A
{
static B b;
}
__declspec(selectany) A::b;
Notez que je ne dis pas que c'est bon, je dis juste que cela peut être fait.
[1] Ces jours-ci, plus de compilateurs que MSC prennent en charge __declspec(selectany)
- au moins gcc et clang. Peut-être même plus.
int foo::i = 0;
Est-ce la syntaxe correcte pour initialiser la variable, mais il faut aller dans le fichier source (.cpp) plutôt que dans l'en-tête.
Comme il s’agit d’une variable statique, le compilateur n’a besoin d’en créer qu’une copie. Vous devez avoir une ligne "int foo: i" quelque part dans votre code pour indiquer au compilateur où le mettre sinon vous obtenez une erreur de lien. Si cela se trouve dans un en-tête, vous en obtiendrez une copie dans chaque fichier contenant cet en-tête. Obtenez donc plusieurs erreurs de symboles définis à partir de l'éditeur de liens.
Si vous voulez initialiser un type composé (par exemple, une chaîne), vous pouvez faire quelque chose comme ça:
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;
}
};
Comme ListInitializationGuard
est une variable statique dans la méthode SomeClass::getList()
, elle ne sera construite qu'une seule fois, ce qui signifie que le constructeur est appelé une fois. Cela va initialize _list
variable à la valeur dont vous avez besoin. Tout appel ultérieur à getList
renverra simplement l'objet _list
déjà initialisé.
Bien sûr, vous devez toujours accéder à l'objet _list
en appelant la méthode getList()
.
Je n'ai pas assez de représentants ici pour ajouter cela en tant que commentaire, mais OMI c'est un bon style d'écrire vos en-têtes avec # incluent les gardes de toute façon, ce qui, comme l'a noté Paranaix il y a quelques heures, empêcherait un multiple erreur de définition. À moins que vous n'utilisiez déjà un fichier CPP distinct, il n'est pas nécessaire de l'utiliser pour initialiser des membres statiques non-intégraux.
#ifndef FOO_H
#define FOO_H
#include "bar.h"
class foo
{
private:
static bar i;
};
bar foo::i = VALUE;
#endif
Je ne vois pas la nécessité d'utiliser un fichier CPP distinct pour cela. Bien sûr, vous le pouvez, mais il n’ya aucune raison technique de le faire.
Modèle de constructeur statique qui fonctionne pour plusieurs objets
Un idiome a été proposé à l'adresse suivante: https://stackoverflow.com/a/27088552/895245 mais voici une version plus propre qui ne nécessite pas la création d'une nouvelle méthode par membre, et un exemple pouvant être exécuté:
#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);
}
Voir aussi: constructeurs statiques en C++? Je dois initialiser des objets statiques privés
Testé avec g++ -std=c++11 -Wall -Wextra
, GCC 7.3, Ubuntu 18.04.
Vous pouvez également inclure l'affectation dans le fichier d'en-tête si vous utilisez des gardes d'en-tête. J'ai utilisé cette technique pour une bibliothèque C++ que j'ai créée. Un autre moyen d'obtenir le même résultat consiste à utiliser des méthodes statiques. Par exemple...
class Foo
{
public:
int GetMyStatic() const
{
return *MyStatic();
}
private:
static int* MyStatic()
{
static int mStatic = 0;
return &mStatic;
}
}
Le code ci-dessus a le "bonus" de ne pas nécessiter de fichier source/RPC. Encore une fois, une méthode que j'utilise pour mes bibliothèques C++.
Je suis l'idée de Karl. Je l'aime et maintenant je l'utilise aussi. J'ai légèrement modifié la notation et ajouté des fonctionnalités
#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));
}
cette sortie
mystatic value 7
mystatic value 3
is my static 1 0
Travaille également dans le fichier 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
Qu'en est-il d'une méthode set_default()
?
class foo
{
public:
static void set_default(int);
private:
static int i;
};
void foo::set_default(int x) {
i = x;
}
Nous n'aurions qu'à utiliser la méthode set_default(int x)
et notre variable static
serait initialisée.
Cela ne serait pas en désaccord avec le reste des commentaires, il suit en fait le même principe d'initialisation de la variable dans une étendue globale, mais en utilisant cette méthode, nous la rendons explicite (et facile à voir-comprendre) au lieu d'avoir la définition. de la variable accrochée là.
Une façon "ancienne" de définir des constantes est de les remplacer par un enum
:
class foo
{
private:
enum {i = 0}; // default type = int
enum: int64_t {HUGE = 1000000000000}; // may specify another type
};
De cette façon, il n’est pas nécessaire de fournir une définition et cela évite de faire la constante lvalue , ce qui peut vous éviter des maux de tête, par exemple. quand vous avez accidentellement ODR-use le.
Le problème de l'éditeur de liens que vous avez rencontré est probablement dû à:
C'est un problème courant pour ceux qui commencent par C++. Un membre de classe statique doit être initialisé dans une seule unité de traduction, c’est-à-dire dans un fichier source unique.
Malheureusement, le membre de classe statique doit être initialisé en dehors du corps de la classe. Cela complique l'écriture de code contenant uniquement des en-têtes et j'utilise donc une approche très différente. Vous pouvez fournir votre objet statique via une fonction de classe statique ou non statique, par exemple:
class Foo
{
// int& getObjectInstance() const {
static int& getObjectInstance() {
static int object;
return object;
}
void func() {
int &object = getValueInstance();
object += 5;
}
};
Je voulais juste mentionner quelque chose d'un peu étrange pour moi quand je l'ai rencontré pour la première fois.
J'avais besoin d'initialiser un membre de données statique privé dans une classe de modèle.
dans le .h ou .hpp, cela ressemble à ceci pour initialiser un membre de données statique d'une classe de modèle:
template<typename T>
Type ClassName<T>::dataMemberName = initialValue;
Est-ce que cela sert votre but?
//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;
}