web-dev-qa-db-fra.com

Comment initialiser des membres statiques privés en C ++?

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?

476
Jason Baker

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;
};
511
Martin York

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;
};
85
Matt Curtis

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 #included plusieurs fois lors de la compilation d’un seul fichier .cpp

26
Joshua Clayton

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;
};
23
Die in Sente

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.

19
Johann Gerell
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.

16
David Dibben

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().

12
Kris Kwiatkowski

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.

11
monkey0506

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

GitHub en amont .

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++.

5
user2225284

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
4
Alejadro Xalabarder

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
3
andrew

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à.

3

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.

2
anatolyg

Le problème de l'éditeur de liens que vous avez rencontré est probablement dû à:

  • Fournir à la fois la définition de la classe et du membre statique dans le fichier d’en-tête,
  • Y compris cet en-tête dans deux ou plusieurs fichiers source.

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;
    }
};
2
no one special

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;
1
Tyler Heers

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