Можно ли использовать статическую переменную для инициализации / регистрации переменных? - PullRequest
1 голос
/ 25 февраля 2010

Язык: 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)
{
}

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

Ответы [ 3 ]

2 голосов
/ 26 февраля 2010

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

В ручном случае это означает вставку и обновление кода в блоке перевода, который содержит main и в main . Наиболее распространенным примером такого кода является вызов srand(time(0)) для заполнения std :: rand 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 для включения зависимостей (так же, как включаемые охранники работают для заголовков).

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

// 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, если вы хотите узнать больше.

1 голос
/ 26 февраля 2010

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

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;
}
1 голос
/ 25 февраля 2010

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

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

    ... 

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...