Est-il acceptable d'utiliser une variable statique pour initialiser / enregistrer les variables?

StackOverflow https://stackoverflow.com/questions/2337446

  •  22-09-2019
  •  | 
  •  

Question

Langue: C ++ Boîte à outils: Qt4

La boîte à outils J'utilise a une méthode statique appelée int QEvent::registerEventType() pour enregistrer mes propres types d'événements. Quand je la sous-classe ce QEvent je dois fournir la classe de base de cette valeur. QEvent::QEvent(int type).

est-il autorisé à utiliser une variable statique pour appeler ce avant le début de l'application? Considérez ce qui suit:

//This is all in my .cpp file

static int myEventType;  //This will contain my registered type

/*If I create a static instance of this class the constructor 
  gets called before the main() function starts.
*/
class DoRegisterMyEventType {  
public:
  DoRegisterMyEventType() {
    myEventType = QEvent::registerEventType();
  }
};

static DoRegisterMyEventType doRegisterMyEventType;

//Here is the constructor for MyEvent class which inherits QEvent.
MyEvent::MyEvent()
  : QEvent(myEventType)
{
}

Comment le « mal » est-ce? Je pourrais envelopper le tout dans un espace de noms pour éviter de polluer l'espace de noms global.

Était-ce utile?

La solution

initialisation du niveau statique est une énorme zone grise dépendant du compilateur, comme d'autres l'ont mentionné. Cependant, l'initialisation du niveau de la fonction n'est pas une zone grise et peut être utilisé à votre avantage.

static inline int GetMyEventType()
{
    static int sEventType = QEvent::registerEventType();
    return sEventType;
}

MyEvent::MyEvent()
  : QEvent(GetMyEventType())
{
}

Cette solution a la propriété que registerEventType est garanti d'être appelé avant d'avoir besoin de votre type d'événement, même si vous construisez MyEvent lors de l'initialisation statique, ce qui est bon, mais il ne vous ouvre jusqu'à Réentrance des problèmes s'il est possible pour MyEvent à construire sur plusieurs threads.

Voici une version thread-safe, basé sur boost :: call_once:

#include "boost/thread/once.hpp"

static boost::once_flag sHaveRegistered = BOOST_ONCE_INIT; //This is initialized statically, effectively at compile time.    
static int sEventType = -1; //-1 is not a valid event

static void DoRegister()
{
    sEventType = QEvent::registerEventType();
}

static inline int GetMyEventType()
{
    boost::call_once(sHaveRegistered, &DoRegister);
    return sEventType;
}

Autres conseils

Depuis l'initialisation de C ++ dans TUs est une grande zone grise avec une marge de manœuvre beaucoup plus de mise en œuvre, je préfère la ferraille complètement et être explicite sur ce qui se fait quand. (Ce rejet de l'ordre d'initialisation en raison du manque de garanties est similaire à la façon dont les classes singleton rejettent les objets globaux.) qui ne peuvent être initialisés avec constant- Plus précisément, cela signifie tout état global (variables globales, les membres de données statiques et statics fonction locale) expressions doivent être initialisées dans exactement un TU, et que TU est celui qui met en œuvre principale .

Dans le cas manuel, cela signifie insérer le code et la mise à jour dans l'unité de traduction qui contient principale et principale lui-même. L'exemple le plus courant de ce code appelle srand(time(0)) aux semences std :: rand PRNG.

Vous pouvez factoriser que la gestion manuelle des codes via le préprocesseur:

// the implementation file for main, could be named main.cpp

#include "whatever_declares_the_real_main.hpp"

#include "global_objects.inc"

int main(int argc, char* argv[]) try {
#include "main_init.inc"

  return the_real_main(argc, argv);

  // main.cpp has well-defined responsibility:
  // initialize global state before passing control to another function, and
  // handle return-code or exceptions

  // you can modify this, depending on your preference and desired API
  // for example:
  return the_real_main(std::vector<std::string>(argv+1, argv+argc));
  return the_real_main(parse_args(argv+1, argv+argc));
  // just make sure to keep main.cpp's responsibility well-defined and
  // relatively simple
}
// example handling; depending on your specifics, you might do something
// different, or know how to provide more information:
catch (std::exception& e) {
  std::cerr << "abnormal termination: " << e.what() << '\n';
  return 1;
}
catch (...) {
  std::cerr << "abnormal termination.\n";
  return 1;
}

Ces fichiers .inc ne sont ni têtes, ni les fichiers d'implémentation. L'extension de fichier exact n'a pas d'importance tant que vous n'utilisez pas quelque chose qui est couramment utilisé pour les en-têtes ou des fichiers de mise en œuvre, tels que .h, .hpp, .cc, Cpp, et ainsi de suite. Vous pouvez générer global_objects.inc et main_init.inc à base de conventions de désignation des fichiers, en utilisant notamment des gardes afin que les dépendances peuvent être inclus (comme inclure le travail des en-têtes des gardes).

Par exemple, ces deux fichiers correspondent avec myevent.hpp et serait placé à côté de cet en-tête:

// file "myevent.global_inc"
#ifndef INCLUDE_GUARD_37E6F5857F8F47918A7C83F29A9DA868
#define INCLUDE_GUARD_37E6F5857F8F47918A7C83F29A9DA868

#include <QEvent.hpp> // or whatever headers you need

#include "myevent.hpp" // declares the variable defined just below
// (remember you use 'extern' to declare objects without defining them)

int your_namespace::myEventType = QEvent::registerEventType();

#endif

// file "myevent.main_inc"
#ifndef INCLUDE_GUARD_4F1B93D0F4D3402B802CBA433241AA81
#define INCLUDE_GUARD_4F1B93D0F4D3402B802CBA433241AA81

// nothing needed in this case, from what you've shown so far

// this is where you place expressions that would otherwise require a dummy
// global variable to make sure they are executed, but this also allows use
// of temporary variables while includes handle dependency order:
#include "something_else.main_inc" // fake example dependency, which must
{                                  // be executed first
  int temp;
  some_func(&temp);
  other_func(temp); // not easy to transform this into a global's init
  // expression, yet defining it this way is natural, because it's exactly
  // how you would do it inside a function
}

#endif

Notez que si vous ne souhaitez une initialisation de données statiques avec-expressions constantes, alors qui est préféré sur toutes les autres techniques. La restriction principale pour que l'initialisation est de ne pas pouvoir faire un appel de fonction (mais il est en fait plus complexe), de sorte qu'il ne s'applique pas dans votre cas; c'est le seul type d'initialisation que variable globale C peut faire, si vous voulez en savoir plus.

J'utilise le modèle « objet registre statique » un peu, mais vous devez être au courant d'un gros problème - vous devez vous assurer que la chose que vous enregistrez avec, qui lui-même est susceptible d'être statique, est créé avant la chose vous enregistrez. En C ++ ne garantit pas l'ordre de construction statique entre les unités de traduction, cela peut être problématique. Une solution est d'utiliser la soi-disant Meyer Singleton:

class Registry {
  public:
    static Registry & Instance() {
        static Registry r;
        return r;
    }

    ... 

 private:
    Registry() {    
      ...
    }
};

Comme toutes les références au registre doivent passer par la méthode instance (), vous êtes assuré l'ordre de construction requis.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top