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

521

Quelle est la meilleure façon d'initialiser un membre de données statique privé en C ++? J'ai essayé cela dans mon fichier d'en-tête, mais cela me donne d'étranges erreurs 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é en dehors de la classe. Alors, quelle est la meilleure façon de procéder?

Jason Baker
la source
2
Salut Jason. Je n'ai pas trouvé de commentaire sur l'initialisation par défaut des membres statiques (en particulier les membres intégraux). En fait, vous devez écrire int foo :: i pour que l'éditeur de liens puisse le trouver, mais il sera automatiquement initialisé avec 0! Cette ligne serait suffisante: int foo :: i; (Ceci est valable pour tous les objets stockés dans la mémoire statique, l'éditeur de liens est en charge de l'initialisation des objets statiques.)
Nico
1
Les réponses ci-dessous ne s'appliquent pas à une classe de modèle. Ils disent: l'initialisation doit aller dans le fichier source. Pour une classe modèle, ce n'est ni possible, ni nécessaire.
Joachim W
7
C ++ 17 permet l' initialisation ligne d'éléments de données statiques (même pour des types non entiers) inline static int x[] = {1, 2, 3};. Voir en.cppreference.com/w/cpp/language/static#Static_data_members
Vladimir Reshetnikov

Réponses:

557

La déclaration de classe doit se trouver dans le fichier d'en-tête (ou dans le fichier source s'il n'est pas partagé).
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, alors chaque fichier qui inclut 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. L'initialisation du static int idoit se faire en dehors de toute fonction.

Note: Matt Curtis: rappelle que C ++ permet la simplification de ce qui précède , si la variable 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;
};
Martin York
la source
4
Oui. Mais je suppose que la question a été simplifiée. Techniquement, la déclaration et la définition peuvent toutes être dans un seul fichier source. Mais cela limite alors l'utilisation de la classe par d'autres classes.
Martin York
11
en fait pas seulement POD, il doit aussi être de type int (int, short, bool, char ...)
Matt Curtis
9
Notez que ce n'est pas seulement une question de comment la valeur est initialisée: les types intégraux const définis comme celui-ci peuvent être transformés en constantes de temps de compilation par l'implémentation. Ce n'est pas toujours ce que vous voulez, car cela augmente la dépendance binaire: le code client a besoin d'une recompilation si la valeur change.
Steve Jessop
5
@Martin: en plus de la correction s / POD / type intégral /, si l'adresse est déjà prise, il doit également y avoir une définition. Aussi étrange que cela puisse paraître, la déclaration avec initialiseur, dans la définition de classe, n'est pas une définition. L' idiome const de modèle fournit une solution de contournement pour les cas où vous avez besoin de la définition dans un fichier d'en-tête. Une autre solution de contournement plus simple est une fonction qui produit la valeur d'une constante statique locale. Cheers & hth.,
Cheers et hth. - Alf
3
Vous pourriez ajouter une clarification que int foo :: i = 0; ne doit pas être à l'intérieur d'une fonction (y compris la fonction principale). Je l'avais au début de ma fonction principale et ça n'aime pas ça.
qwerty9967
89

Pour une 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::idans votre programme. C'est en quelque sorte l'équivalent de extern int idans un fichier d' en-tête et int idans 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;
};
Matt Curtis
la source
2
Ceci est un point valable. J'ajouterai cela aussi mon explication. Mais il convient de noter que cela ne fonctionne que pour les types de POD.
Martin York
Depuis lors, C ++ permet d'être juste avec une déclaration en classe et aucune définition pour les types intégraux. Depuis C ++ 98 lui-même ou C ++ 03 ou quand? Veuillez partager des liens authentiques, s'il vous plaît. La formulation standard C ++ n'est pas synchronisée avec les compilateurs. Ils mentionnent que le membre sera toujours défini s'il est utilisé. Donc, je n'ai pas besoin de citer le standard C ++
smRaj
1
Je me demande pourquoi les privatevariables peuvent être initialisées en dehors de Class ici, cela peut-il être fait pour les variables non statiques également.
Krishna Oza
Avez-vous trouvé l'explication? @Krishna_Oza
nn0p
@ nn0p pas encore, mais l'initialisation des variables privées non statiques à l'extérieur Classn'a aucun sens dans Cpp.
Krishna Oza
42

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 statiques peut être déclaré en ligne. Un membre de données statiques 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;
};
Die in Sente
la source
1
Cela est possible depuis le C ++ 17, qui est en train de devenir le nouveau standard.
Grebu
31

Pour les futurs téléspectateurs de cette question, je tiens à souligner que vous devez éviter ce que suggère monkey0506 .

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 .cppfichier qui les a directement ou indirectement #includes, et le code en dehors de toute fonction est exécuté avant l'initialisation du programme main().

En mettant: foo::i = VALUE;dans l'en-tête, foo:isera attribuée la valeur VALUE(quelle qu'elle soit) pour chaque .cppfichier, et ces affectations se produiront dans un ordre indéterminé (déterminé par l'éditeur de liens) avant l' main()exécution.

Et si nous devions #define VALUEêtre un numéro différent dans l'un de nos .cppfichiers? Il compilera très bien et nous n'aurons aucun moyen de savoir lequel gagnera jusqu'à ce que nous exécutions le programme.

Ne jamais mettre le code exécuté dans un en- tête pour la même raison que vous ne #includeun .cppfichier.

inclure des gardes (que je conviens que vous devriez toujours utiliser) vous protègent de quelque chose de différent: le même en-tête étant indirectement #includeplusieurs fois d lors de la compilation d'un seul .cppfichier

Joshua Clayton
la source
2
Vous avez raison à ce sujet bien sûr, sauf dans le cas d'un modèle de classe (qui n'est pas demandé, mais il m'arrive d'avoir beaucoup à faire). Donc, si la classe est entièrement définie et non un modèle de classe, placez ces membres statiques dans un fichier CPP distinct, mais pour les modèles de classe, la définition doit être dans la même unité de traduction (par exemple, le fichier d'en-tête).
monkey0506
@ monkey_05_06: Cela semble juste être un argument pour éviter un membre statique dans le code modèle: vous vous retrouvez déjà avec un membre statique pour chaque instanciation de la classe. le problème est aggravé par la compilation éventuelle de l'en-tête dans plusieurs fichiers cpp ... Vous pouvez obtenir un ensemble de définitions conflictuelles.
Joshua Clayton
publib.boulder.ibm.com/infocenter/macxhelp/v6v81/… Ce lien décrit l'instanciation des membres du modèle statique dans la fonction principale, qui est plus propre, même si c'est un peu un fardeau.
Joshua Clayton
1
Votre argument est vraiment énorme. D'abord, vous ne pouvez pas # définir la valeur car le nom des macros ne doit pas être un identifiant valide. Et même si vous pouviez - qui ferait cela? Les fichiers d'en-tête sont destinés à la déclaration -? Allez .. Les seuls cas où vous devriez éviter de mettre des valeurs dans l'en-tête est de combattre l'odr-used. Et la mise de la valeur dans l'en-tête peut entraîner une recompilation inutile chaque fois que vous devez modifier la valeur.
Aleksander Fular
20

Avec un compilateur Microsoft [1], des variables statiques qui ne sont pas intsimilaires peuvent également être définies dans un fichier d'en-tête, mais en dehors de la déclaration de classe, à l'aide de Microsoft specific __declspec(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] De nos jours, plus de compilateurs que le support MSC __declspec(selectany)- au moins gcc et clang. Peut-être même plus.

Johann Gerell
la source
17
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.

Puisqu'il s'agit d'une variable statique, le compilateur n'a besoin d'en créer qu'une seule 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 obtiendrez une copie dans chaque fichier qui comprend l'en-tête, alors obtenez des erreurs de symboles définies multipliées de l'éditeur de liens.

David Dibben
la source
12

Je n'ai pas assez de représentants ici pour ajouter ceci en tant que commentaire, mais à mon avis, c'est un bon style d'écrire vos en-têtes avec les gardes #include de toute façon, ce qui, comme l'a noté 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 d'en utiliser un uniquement pour initialiser les 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 pouvez, mais il n'y a aucune raison technique pour laquelle vous devriez le faire.

monkey0506
la source
21
#include guards empêche simplement plusieurs définitions par unité de traduction.
Paul Fultz II
3
concernant le bon style: vous devriez ajouter un commentaire sur la fin de la finale:#endif // FOO_H
Riga
9
Cela ne fonctionne que si vous n'avez qu'une seule unité de compilation qui inclut foo.h. Si deux ou plusieurs cpps incluent foo.h, ce qui est une situation typique, chaque cpp déclarera la même variable statique de sorte que l'éditeur de liens se plaindra avec plusieurs définitions de `foo :: i 'à moins que vous n'utilisiez une compilation de package avec les fichiers (compile un seul fichier qui inclut tous les cpps). Mais bien que la compilation de paquets soit excellente, la solution au problème est de déclarer (int foo :: i = 0;) dans un cpp!
Alejadro Xalabarder
1
Ou utilisez simplement#pragma once
tambre
12

Si vous voulez initialiser un type composé (fe 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 la méthode ListInitializationGuardest une variable statique à l'intérieur SomeClass::getList(), elle ne sera construite qu'une seule fois, ce qui signifie que le constructeur est appelé une seule fois. Cela initialize _listvariera selon la valeur dont vous avez besoin. Tout appel ultérieur à getListretournera simplement un _listobjet déjà initialisé .

Bien sûr, vous devez _listtoujours accéder à l' objet en appelant la getList()méthode.

Kris Kwiatkowski
la source
1
Voici une version de cet idiome qui ne nécessite pas de créer une méthode par objet membre: stackoverflow.com/a/48337288/895245
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
9

Modèle de constructeur statique C ++ 11 qui fonctionne pour plusieurs objets

Un idiome a été proposé sur: https://stackoverflow.com/a/27088552/895245 mais voici une version plus propre qui ne nécessite pas de créer une nouvelle méthode par membre.

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 en amont .

Compiler et exécuter:

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

Voir aussi: constructeurs statiques en C ++? J'ai besoin d'initialiser des objets statiques privés

Testé sur Ubuntu 19.04.

Variable en ligne C ++ 17

Mentionné à: https://stackoverflow.com/a/45062055/895245 mais voici un exemple exécutable multifichier pour le rendre encore plus clair: Comment fonctionnent les variables en ligne?

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

Vous pouvez également inclure l'affectation dans le fichier d'en-tête si vous utilisez des protections d'en-tête. J'ai utilisé cette technique pour une bibliothèque C ++ que j'ai créée. Une autre façon 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 CPP / source. Encore une fois, une méthode que j'utilise pour mes bibliothèques C ++.


la source
4

Je suis l'idée de Karl. Je l'aime et maintenant je l'utilise aussi. J'ai un peu changé la notation et ajouté quelques 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));
}

cela produit

mystatic value 7
mystatic value 3
is my static 1 0
Alejadro Xalabarder
la source
3

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

Et une set_default()méthode?

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 set_default(int x)méthode et notre staticvariable serait initialisée.

Ce ne serait pas en désaccord avec le reste des commentaires, en fait, cela suit 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 d'avoir la définition de la variable qui pend là.

Arturo Ruiz Mañas
la source
3

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

  • Fournir la définition de classe et de membre statique dans le fichier d'en-tête,
  • Inclure cet en-tête dans deux fichiers source ou plus.

Il s'agit d'un problème courant pour ceux qui commencent par C ++. Le 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 classe. Cela complique l'écriture de code uniquement en-tête et, par conséquent, j'utilise une approche assez 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;
    }
};
personne de spécial
la source
1
Je suis toujours un n00b complet en ce qui concerne C ++, mais cela me semble génial, merci beaucoup! Je reçois gratuitement la gestion parfaite du cycle de vie de l'objet singleton.
Rafael Kitover
2

Une façon «ancienne» de définir des constantes consiste à les remplacer par enum:

class foo
{
    private:
        enum {i = 0}; // default type = int
        enum: int64_t {HUGE = 1000000000000}; // may specify another type
};

De cette façon , ne nécessite pas de donner une définition et évite de faire la constante lvalue , ce qui peut vous faire économiser quelques maux de tête, par exemple lorsque vous avez accidentellement ODR utiliser ce.

anatolyg
la source
1

Je voulais juste mentionner quelque chose d'un peu étrange pour moi lorsque j'ai rencontré cela pour la première fois.

J'avais besoin d'initialiser un membre de données statiques privé dans une classe de modèle.

dans le .h ou .hpp, cela ressemble à ceci pour initialiser un membre de données statiques d'une classe de modèle:

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

Cela sert-il votre objectif?

//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
la source