web-dev-qa-db-fra.com

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

Je veux avoir une classe avec un membre de données statique privé (un vecteur qui contient tous les caractères a-z). En Java ou en C #, je peux simplement créer un "constructeur statique" qui s'exécutera avant de créer des occurrences de la classe et configurer les données statiques membres de la classe. Il ne s'exécute qu'une fois (les variables étant en lecture seule et ne devant être définies qu'une seule fois) et puisqu'il s'agit d'une fonction de la classe, il peut accéder à ses membres privés. Je pourrais ajouter du code dans le constructeur qui vérifie si le vecteur est initialisé et l'initialise si ce n'est pas le cas, mais cela introduit de nombreuses vérifications nécessaires et ne semble pas être la solution optimale au problème.

L'idée me vient à l'esprit que, puisque les variables seront en lecture seule, elles ne peuvent être que des const statiques publiques. Je peux donc les placer une fois en dehors de la classe, mais encore une fois, cela ressemble un peu à un mauvais bidouillage. 

Est-il possible d'avoir des membres de données statiques privés dans une classe si je ne veux pas les initialiser dans le constructeur d'instance?

161
Gordon Gustafson

Pour obtenir l'équivalent d'un constructeur statique, vous devez écrire une classe ordinaire distincte pour contenir les données statiques, puis créer une instance statique de cette classe ordinaire.

class StaticStuff
{
     std::vector<char> letters_;

public:
     StaticStuff()
     {
         for (char c = 'a'; c <= 'z'; c++)
             letters_.Push_back(c);
     }

     // provide some way to get at letters_
};

class Elsewhere
{
    static StaticStuff staticStuff; // constructor runs once, single instance

};
169
Daniel Earwicker

Bien tu peux avoir

class MyClass
{
    public:
        static vector<char> a;

        static class _init
        {
          public:
            _init() { for(char i='a'; i<='z'; i++) a.Push_back(i); }
        } _initializer;
};

N'oubliez pas (dans le .cpp) ceci:

vector<char> MyClass::a;
MyClass::_init MyClass::_initializer;

Le programme sera toujours lié sans la deuxième ligne, mais l'initialiseur ne sera pas exécuté.

73
EFraim

Dans le fichier .h:

class MyClass {
private:
    static int myValue;
};

Dans le fichier .cpp:

#include "myclass.h"

int MyClass::myValue = 0;
19
Ant

Solution C++ 11

Depuis C++ 11, vous pouvez utiliser les expressions lambda to directement initialisent les membres statiques de la classe. Vous n'avez plus besoin de recourir à des solutions de contournement.

En tête de fichier:

class MyClass {
    static vector<char> letters;
};

Fichier source:

vector<char> MyClass::letters = [] {
    vector<char> letters;
    for (char c = 'a'; c <= 'z'; c++)
        letters.Push_back(c);
    return letters;
}();

Ce modèle est un remplacement complet pour les constructeurs statiques:

  • Vous avez un ordre initialization bien défini pour tous vos membres statiques: simplement le même ordre que celui défini dans le fichier source.
  • Vous pouvez à la fois lire et écrire d'autres membres statiques (déjà initialisés!) Dans l'expression lambda.
  • Vous pouvez initialiser des membres statiques qui dépendent d'autres membres statiques. Vous devez juste les initialiser dans le bon ordre.
15
emkey08

Voici une autre approche similaire à celle de Daniel Earwicker, utilisant également la suggestion de classe d'amis de Konrad Rudolph. Ici, nous utilisons une classe d'utilitaires d'amis privés internes pour initialiser les membres statiques de votre classe principale. Par exemple:

En tête de fichier:

class ToBeInitialized
{
    // Inner friend utility class to initialize whatever you need

    class Initializer
    {
    public:
        Initializer();
    };

    friend class Initializer;

    // Static member variables of ToBeInitialized class

    static const int numberOfFloats;
    static float *theFloats;

    // Static instance of Initializer
    //   When this is created, its constructor initializes
    //   the ToBeInitialized class' static variables

    static Initializer initializer;
};

Fichier d'implémentation:

// Normal static scalar initializer
const int ToBeInitialized::numberOfFloats = 17;

// Constructor of Initializer class.
//    Here is where you can initialize any static members
//    of the enclosing ToBeInitialized class since this inner
//    class is a friend of it.

ToBeInitialized::Initializer::Initializer()
{
    ToBeInitialized::theFloats =
        (float *)malloc(ToBeInitialized::numberOfFloats * sizeof(float));

    for (int i = 0; i < ToBeInitialized::numberOfFloats; ++i)
        ToBeInitialized::theFloats[i] = calculateSomeFancyValue(i);
}

Cette approche a l’avantage de cacher complètement la classe Initializer au monde extérieur, tout en conservant tout ce qui est contenu dans la classe à initialiser.

14
Douglas Mandell

Test::StaticTest() est appelé exactement une fois lors de l'initialisation statique globale.

L'appelant n'a qu'à ajouter une ligne à la fonction qui sera son constructeur statique.

static_constructor<&Test::StaticTest>::c; force l'initialisation de c lors de l'initialisation statique globale.

template<void(*ctor)()>
struct static_constructor
{
    struct constructor { constructor() { ctor(); } };
    static constructor c;
};

template<void(*ctor)()>
typename static_constructor<ctor>::constructor static_constructor<ctor>::c;

/////////////////////////////

struct Test
{
    static int number;

    static void StaticTest()
    {
        static_constructor<&Test::StaticTest>::c;

        number = 123;
        cout << "static ctor" << endl;
    }
};

int Test::number;

int main(int argc, char *argv[])
{
    cout << Test::number << endl;
    return 0;
}
10
bitwise

Pas besoin d'une fonction init(), std::vector peut être créé à partir d'une plage:

// h file:
class MyClass {
    static std::vector<char> alphabet;
// ...
};

// cpp file:
#include <boost/range.hpp>
static const char alphabet[] = "abcdefghijklmnopqrstuvwxyz";
std::vector<char> MyClass::alphabet( boost::begin( ::alphabet ), boost::end( ::alphabet ) );

Notez cependant que les statiques de type classe posent des problèmes dans les bibliothèques, elles doivent donc être évitées là-bas.

Mise à jour C++ 11

A partir de C++ 11, vous pouvez le faire à la place:

// cpp file:
std::vector<char> MyClass::alphabet = { 'a', 'b', 'c', ..., 'z' };

Cela ressemble sémantiquement à la solution C++ 98 de la réponse d'origine, mais vous ne pouvez pas utiliser de littéral chaîne du côté droit, ce n'est donc pas complètement supérieur. Cependant, si vous avez un vecteur d'un autre type que char, wchar_t, char16_t ou char32_t (dont les tableaux peuvent être écrits en tant que chaînes de caractères), la version C++ 11 supprimera strictement le code standard sans introduire d'autre syntaxe, par rapport à la version précédente. Version C++ 98.

9
Marc Mutz - mmutz

Le concept de constructeur statique a été introduit en Java après avoir appris des problèmes en C++. Nous n’avons donc pas d’équivalent direct.

La meilleure solution consiste à utiliser des types de POD pouvant être initialisés explicitement.
Ou faites de vos membres statiques un type spécifique ayant son propre constructeur qui l'initialisera correctement.

//header

class A
{
    // Make sure this is private so that nobody can missues the fact that
    // you are overriding std::vector. Just doing it here as a quicky example
    // don't take it as a recomendation for deriving from vector.
    class MyInitedVar: public std::vector<char>
    {
        public:
        MyInitedVar()
        {
           // Pre-Initialize the vector.
           for(char c = 'a';c <= 'z';++c)
           {
               Push_back(c);
           }
        }
    };
    static int          count;
    static MyInitedVar  var1;

};


//source
int            A::count = 0;
A::MyInitedVar A::var1;
6
Martin York

En essayant de compiler et utiliser class Elsewhere (de réponse d'Earwicker ), je reçois:

error LNK2001: unresolved external symbol "private: static class StaticStuff Elsewhere::staticStuff" (?staticStuff@Elsewhere@@0VStaticStuff@@A)

Il semble qu'il n'est pas possible d'initialiser des attributs statiques de types non entiers sans mettre du code en dehors de la définition de classe (CPP).

Pour effectuer cette compilation, vous pouvez utiliser "une méthode statique avec une variable locale statique à l'intérieur de". Quelque chose comme ça:

class Elsewhere
{
public:
    static StaticStuff& GetStaticStuff()
    {
        static StaticStuff staticStuff; // constructor runs once, single instance
        return staticStuff;
    }
};

Et vous pouvez aussi passer des arguments au constructeur ou l'initialiser avec des valeurs spécifiques, c'est très flexible, puissant et facile à implémenter ... le seul problème est que vous avez une méthode statique contenant une variable statique, pas un attribut statique ... la syntaxe change un peu, mais reste utile. J'espère que c'est utile pour quelqu'un,

Hugo González Castro.

4
user260858

Je suppose que la solution simple à ce sera:

    //X.h
    #pragma once
    class X
    {
    public:
            X(void);
            ~X(void);
    private:
            static bool IsInit;
            static bool Init();
    };

    //X.cpp
    #include "X.h"
    #include <iostream>

    X::X(void)
    {
    }


    X::~X(void)
    {
    }

    bool X::IsInit(Init());
    bool X::Init()
    {
            std::cout<< "ddddd";
            return true;
    }

    // main.cpp
    #include "X.h"
    int main ()
    {
            return 0;
    }
4
Shubham

Juste résolu le même truc. Je devais spécifier la définition d’un seul membre statique pour Singleton ..__ Mais compliquez les choses - j’ai décidé que je ne voudrais pas appeler ctor de RandClass () à moins que je ne l’utilise ne voulait pas initialiser singleton globalement dans mon code. Aussi, j'ai ajouté une interface simple dans mon cas.

Voici le code final:

J'ai simplifié le code et utilisé la fonction Rand () et sa graine unique initialzer srand ()

interface IRandClass
{
 public:
    virtual int GetRandom() = 0;
};

class RandClassSingleton
{
private:
  class RandClass : public IRandClass
  {
    public:
      RandClass()
      {
        srand(GetTickCount());
      };

     virtual int GetRandom(){return Rand();};
  };

  RandClassSingleton(){};
  RandClassSingleton(const RandClassSingleton&);

  // static RandClass m_Instance;

  // If you declare m_Instance here you need to place
  // definition for this static object somewhere in your cpp code as
  // RandClassSingleton::RandClass RandClassSingleton::m_Instance;

  public:

  static RandClass& GetInstance()
  {
      // Much better to instantiate m_Instance here (inside of static function).
      // Instantiated only if this function is called.

      static RandClass m_Instance;
      return m_Instance;
  };
};

main()
{
    // Late binding. Calling RandClass ctor only now
    IRandClass *p = &RandClassSingleton::GetInstance();
    int randValue = p->GetRandom();
}
abc()
{
    IRandClass *same_p = &RandClassSingleton::GetInstance();
}
1
adspx5

Offres GCC

__attribute__((constructor))

https://gcc.gnu.org/onlinedocs/gcc-4.7.0/gcc/Function-Attributes.html

Marquez une méthode statique avec cet attribut et il sera exécuté lors du chargement du module, avant main ().

1
jws

Voici une autre méthode, où le vecteur est privé du fichier contenant l'implémentation en utilisant un espace de noms anonyme. C'est utile pour des choses comme les tables de recherche privées à l'implémentation:

#include <iostream>
#include <vector>
using namespace std;

namespace {
  vector<int> vec;

  struct I { I() {
    vec.Push_back(1);
    vec.Push_back(3);
    vec.Push_back(5);
  }} i;
}

int main() {

  vector<int>::const_iterator end = vec.end();
  for (vector<int>::const_iterator i = vec.begin();
       i != end; ++i) {
    cout << *i << endl;
  }

  return 0;
}
1
Jim Hunziker

Cela n’a certainement pas besoin d’être aussi compliqué que la réponse actuellement acceptée (par Daniel Earwicker). La classe est superflue. Il n'y a pas besoin de guerre linguistique dans ce cas.

fichier .hpp:

vector<char> const & letters();

fichier .cpp:

vector<char> const & letters()
{
  static vector<char> v = {'a', 'b', 'c', ...};
  return v;
}
1
AndyJost

Voici ma variante de la solution EFraim; la différence est que, grâce à l'instanciation implicite de modèle, le constructeur statique n'est appelé que si des instances de la classe sont créées et qu'aucune définition dans le fichier .cpp n'est nécessaire (grâce à la magie d'instanciation de modèle).

Dans le fichier .h, vous avez:

template <typename Aux> class _MyClass
{
    public:
        static vector<char> a;
        _MyClass() {
            (void) _initializer; //Reference the static member to ensure that it is instantiated and its initializer is called.
        }
    private:
        static struct _init
        {
            _init() { for(char i='a'; i<='z'; i++) a.Push_back(i); }
        } _initializer;

};
typedef _MyClass<void> MyClass;

template <typename Aux> vector<char> _MyClass<Aux>::a;
template <typename Aux> typename _MyClass<Aux>::_init _MyClass<Aux>::_initializer;

Dans le fichier .cpp, vous pouvez avoir:

void foobar() {
    MyClass foo; // [1]

    for (vector<char>::iterator it = MyClass::a.begin(); it < MyClass::a.end(); ++it) {
        cout << *it;
    }
    cout << endl;
}

Notez que MyClass::a est initialisé uniquement si la ligne [1] est présente, car elle appelle (et requiert l'instanciation du) constructeur, ce qui nécessite ensuite une instanciation de _initializer.

1
Blaisorblade

Wow, je ne peux pas croire que personne n'ait mentionné la réponse la plus évidente, et celle qui imite le plus fidèlement le comportement du constructeur statique de C #, c.-à-d. Qu'il ne soit pas appelé jusqu'à ce que le premier objet de ce type soit créé.

std::call_once() est disponible en C++ 11; si vous ne pouvez pas l'utiliser, vous pouvez le faire avec une variable de classe booléenne statique et une opération atomique de comparaison et d'échange. Dans votre constructeur, voyez si vous pouvez modifier de manière atomique l'indicateur de classe statique de false en true et, dans l'affirmative, vous pouvez exécuter le code de construction statique.

Pour plus de crédit, faites-en un drapeau à trois voies au lieu d’un booléen, c’est-à-dire que vous ne courez pas, et que vous avez terminé. Toutes les autres instances de cette classe peuvent alors être verrouillées jusqu'à ce que l'instance qui exécute le constructeur statique ait terminé (c'est-à-dire émettre une barrière de mémoire, puis définir l'état sur "done running"). Votre spin-lock doit exécuter l'instruction "pause" du processeur, doubler l'attente chaque fois jusqu'à un seuil, etc. - technique de verrouillage de spin assez classique.

En l'absence de C++ 11, this devrait vous aider à démarrer.

Voici un pseudocode pour vous guider. Mettez ceci dans votre définition de classe:

enum EStaticConstructor { kNotRun, kRunning, kDone };
static volatile EStaticConstructor sm_eClass = kNotRun;

Et ceci dans votre constructeur:

while (sm_eClass == kNotRun)
{
    if (atomic_compare_exchange_weak(&sm_eClass, kNotRun, kRunning))
    {
        /* Perform static initialization here. */

        atomic_thread_fence(memory_order_release);
        sm_eClass = kDone;
    }
}
while (sm_eClass != kDone)
    atomic_pause();
0
ulatekh

Vous définissez des variables de membre statiques de la même manière que vous définissez les méthodes de membre.

foo.h

class Foo
{
public:
    void bar();
private:
    static int count;
};

foo.cpp

#include "foo.h"

void Foo::bar()
{
    // method definition
}

int Foo::count = 0;
0
Nick Lewis

Pour initialiser une variable statique, il vous suffit de le faire dans un fichier source. Par exemple:

//Foo.h
class Foo
{
 private:
  static int hello;
};


//Foo.cpp
int Foo::hello = 1;
0
Cristián Romo

Pourquoi ne pas créer un modèle pour imiter le comportement de C #.

template<class T> class StaticConstructor
{
    bool m_StaticsInitialised = false;

public:
    typedef void (*StaticCallback)(void);

    StaticConstructor(StaticCallback callback)
    {
        if (m_StaticsInitialised)
            return;

        callback();

        m_StaticsInitialised = true;
    }
}

template<class T> bool StaticConstructor<T>::m_StaticsInitialised;

class Test : public StaticConstructor<Test>
{
    static std::vector<char> letters_;

    static void _Test()
    {
        for (char c = 'a'; c <= 'z'; c++)
            letters_.Push_back(c);
    }

public:
    Test() : StaticConstructor<Test>(&_Test)
    {
        // non static stuff
    };
};
0
karmasponge

Un constructeur statique peut être émulé en utilisant une classe d'amis ou une classe imbriquée comme ci-dessous.

class ClassStatic{
private:
    static char *str;
public:
    char* get_str() { return str; }
    void set_str(char *s) { str = s; }
    // A nested class, which used as static constructor
    static class ClassInit{
    public:
        ClassInit(int size){ 
            // Static constructor definition
            str = new char[size];
            str = "How are you?";
        }
    } initializer;
};

// Static variable creation
char* ClassStatic::str; 
// Static constructor call
ClassStatic::ClassInit ClassStatic::initializer(20);

int main() {
    ClassStatic a;
    ClassStatic b;
    std::cout << "String in a: " << a.get_str() << std::endl;
    std::cout << "String in b: " << b.get_str() << std::endl;
    a.set_str("I am fine");
    std::cout << "String in a: " << a.get_str() << std::endl;
    std::cout << "String in b: " << b.get_str() << std::endl;
    std::cin.ignore();
}

Sortie:

String in a: How are you?
String in b: How are you?
String in a: I am fine
String in b: I am fine
0
Jobin

Est-ce une solution?

class Foo
{
public:
    size_t count;
    Foo()
    {
        static size_t count = 0;
        this->count = count += 1;
    }
};
0
BSalita

Pour des cas simples comme ici, une variable statique encapsulée dans une fonction membre static est presque aussi bonne. C'est simple et sera généralement optimisé par les compilateurs. Cela ne résout cependant pas le problème d'ordre d'initialisation pour les objets complexes.

#include <iostream>

class MyClass 
{

    static const char * const letters(void){
        static const char * const var = "abcdefghijklmnopqrstuvwxyz";
        return var;
    }

    public:
        void show(){
            std::cout << letters() << "\n";
        }
};


int main(){
    MyClass c;
    c.show();
}
0
kriss