Question

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, quel est le meilleur moyen de le faire?

Était-ce utile?

La solution

La déclaration de la 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 se trouve 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 sources.

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

Autres conseils

Pour une variable :

foo.h:

class foo
{
private:
    static int i;
};

foo.cpp:

int foo::i = 0;

Cela est dû au fait qu'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 une 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 lecteurs de cette question, je tiens à souligner que vous devez éviter ce que monkey0506 suggère .

Les fichiers d'en-tête sont destinés aux déclarations.

Les fichiers d'en-tête sont compilés une fois pour chaque fichier .cpp qui les indirectement # inclut , et le code en dehors de toute fonction est exécuté à l'initialisation du programme, avant le main () .

En mettant: foo :: i = VALUE; dans l'en-tête, foo: i recevra la valeur VALUE (peu importe ce que is) pour chaque fichier .cpp , et ces affectations auront lieu dans un ordre indéterminé (déterminé par l'éditeur de liens) avant l'exécution de main () .

Et si #define VALUE était un numéro différent dans l'un de nos fichiers .cpp ? La compilation sera bonne 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 #incluez jamais un fichier .cpp .

inclure des gardes (que vous devez toujours utiliser, je suis d'accord) 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 .cpp fichier

Depuis C ++ 17, les membres statiques peuvent être définis dans l'en-tête à l'aide du mot clé inline .

http://fr.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 d'une définition hors classe: "

struct X
{
    inline static int n = 1;
};

Avec un compilateur Microsoft [1], les variables statiques qui ne ressemblent pas à int peuvent également être définies dans un fichier d'en-tête, mais en dehors de la déclaration de classe, à l'aide du __ declspec spécifique à Microsoft (selectany) .

class A
{
    static B b;
}

__declspec(selectany) A::b;

Notez que je ne dis pas que c'est bon, je dis simplement que cela peut être fait.

[1] Ces jours-ci, davantage de compilateurs que MSC prennent en charge __ declspec (selectany) - au moins gcc et clang. Peut-être même plus.

int foo::i = 0; 

Est la syntaxe correcte pour initialiser la variable, mais elle doit 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 " certains 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 comprenant cet en-tête. Il est donc important que l'éditeur de liens multiplie les erreurs de symbole définies.

Je n'ai pas assez de représentants ici pour ajouter cela en tant que commentaire, mais IMO c'est un bon style d'écrire vos en-têtes avec # inclut des gardes de toute façon, ce qui, comme le notait Paranaix il y a quelques heures, empêcherait une erreur de définition multiple. À 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 à cet effet. Bien sûr, vous le pouvez, mais il n’ya aucune raison technique de le faire.

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 () , il ne sera construit qu'une seule fois, ce qui signifie que le constructeur est appelé une fois. Cela va initialiser _list à la valeur dont vous avez besoin. Tout appel ultérieur à getList renverra simplement un objet _list déjà initialisé.

Vous devez bien sûr accéder à l'objet _list en appelant toujours la méthode getList () .

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 contient le " bonus " de ne pas exiger un fichier source / CPP. 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 un peu 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

Modèle de constructeur statique qui fonctionne pour plusieurs objets

Un idiome a été proposé à l'adresse https://stackoverflow.com/a/27088552/895245 , mais voici une Une version plus propre qui ne nécessite pas de créer 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 ++? J'ai besoin d'initialiser des objets statiques privés

Testé avec g ++ -std = c ++ 11 -Wall -Wextra , GCC 7.3, Ubuntu 18.04.

Fonctionne é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 statique serait initialisée.

Ceci 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 portée globale, mais en utilisant cette méthode, nous la rendons explicite (et facile à voir-comprendre) au lieu de ayant la définition de la variable suspendue à cet endroit.

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

  • Fournissant les définitions de classe et de membre statique dans le fichier d'en-tête,
  • Inclusion de cet en-tête dans plusieurs fichiers sources.

Il s'agit d'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 seul fichier source.

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, par conséquent, j'utilise 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.

Je devais initialiser un membre de données statique privé dans une classe de modèle.

dans le fichier .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;

One " old-school " façon 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 d'éviter de créer la constante lvalue , ce qui peut vous éviter certains maux de tête. , par exemple lorsque vous l'avez utilisé par ODR , utilisez-le.

Cela vous convient-il?

//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;
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top