Нормально ли использовать статическую переменную для инициализации / регистрации переменных?

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

  •  22-09-2019
  •  | 
  •  

Вопрос

Язык:C ++ Инструментарий:Qt4

Инструментарий, который я использую, имеет статический метод, называемый int QEvent::registerEventType() регистрировать свои собственные типы событий.Когда я создаю подкласс этого QEvent Мне нужно предоставить базовому классу это значение. QEvent::QEvent(int type).

Можно ли использовать статическую переменную для вызова этого перед запуском приложения?Рассмотрим следующее:

//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)
{
}

Насколько это "зло"?Я мог бы обернуть все это в пространство имен, чтобы предотвратить загрязнение глобального пространства имен.

Это было полезно?

Решение

Инициализация статического уровня - это огромная серая область, зависящая от компилятора, как уже упоминали другие.Однако инициализация на уровне функций не является серой областью и может быть использована в ваших интересах.

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

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

Это решение обладает тем свойством, что registerEventType гарантированно вызывается до того, как вам понадобится ваш тип события, даже если вы создаете MyEvent во время статической инициализации, что хорошо, но это открывает вам проблемы потокобезопасности, если MyEvent можно создать в нескольких потоках.

Вот потокобезопасная версия, основанная на 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;
}

Другие советы

Начиная с инициализации C ++ через ТУс это большая серая область с большой свободой реализации, я предпочитаю полностью отказаться от нее и четко указать, что и когда будет сделано.(Этот отказ от порядка инициализации из-за отсутствия гарантий аналогичен тому, как одноэлементные классы отклонять глобальные объекты.) В частности, это означает, что любое глобальное состояние (глобальные переменные, статические элементы данных и локальная статика функций), которое не может быть инициализировано с помощью постоянных выражений, должно быть инициализировано ровно одним TU, и этот TU является тем, который реализует Главная.

В случае ручного перевода это означает вставку и обновление кода в блоке перевода, который содержит Главная и в Главная сам по себе.Наиболее распространенным примером такого кода является вызов srand(time(0)) чтобы засеять std:: ранд PRNG.

Вы можете реорганизовать это ручное управление кодом с помощью препроцессора:

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

Эти файлы .inc не являются ни заголовками, ни файлами реализации.Точное расширение файла не имеет значения до тех пор, пока вы не используете что-то, что обычно используется для заголовков или файлов реализации, таких как .h, .hpp, .cc, .cpp и так далее.Вы можете генерировать global_objects.inc и main_init.inc основываясь на соглашениях об именовании файлов, используя include guard, чтобы можно было включать зависимости (точно так же, как include guard работает для заголовков).

Например, оба этих файла соответствуют myevent.гэс и был бы помещен рядом с этим заголовком:

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

Обратите внимание, что если вам требуется только статическая инициализация данных с помощью константных выражений, то это предпочтительнее всех других методов.Основным ограничением для этой инициализации является невозможность выполнить вызов функции (но на самом деле это более сложно), поэтому в вашем случае это неприменимо;это единственный вид инициализации глобальной переменной, который может выполнить C, если вы хотите узнать больше.

Я довольно часто использую шаблон "статический объект регистрации", но вы должны знать об одной большой проблеме - вы должны убедиться, что объект, с помощью которого вы регистрируетесь, который сам по себе, вероятно, будет статичным, создан до объекта, который вы регистрируете.Поскольку C ++ не гарантирует порядок статического построения между единицами перевода, это может быть проблематично.Одним из решений является использование так называемого синглтона Мейера:

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

    ... 

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

Поскольку все ссылки на реестр должны проходить через метод Instance(), вам гарантируется требуемый порядок построения.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top