Главная » C++ энциклопедия » И » Инициализация глобальных объектов
Категория: C++ энциклопедия » И
  • 0

15 дек 2011

Автор: admin

В спецификации языка порядок конструирования глобальных объектов выглядит довольно сложно. Если же учесть причуды коммерческих компиляторов C++, этот порядок становится и вовсе непредсказуемым. В соответствии со спецификацией должны вызываться конструкторы глобальных объектов, включая конструкторы статических переменных классов и структур, однако многие компиляторы этого не делают. Если вам повезло и ваш компилятор считает, что конструкторы важны для глобальных переменных, порядок конструирования глобальных объектов зависит от воображения разработчика компилятора. Ниже перечислены некоторые правила, которые теоретически должны соблюдаться:

  1. Перед выполнением каких-либо операций все глобальные переменные инициализируются значением 0.
  2. Объекты, находящиеся в глобальных структурах или массивах, конструируются в порядке их появления в структуре или массиве.
  3. Каждый глобальный объект конструируется до его первого использования в программе. Компилятор сам решает, следует ли выполнить инициализацию до вызова функции main() или отложить ее до первого использования объекта.
  4. Глобальные объекты, находящиеся в одном «модуле трансляции» (обычно файле с расширением .срр), инициализируются в порядке их появления в этом модуле. В сочетании с правилом 3 это означает, что инициализация может выполняться по модулям, при первом использовании каждого модуля.

Вот и все. Внешне простая последовательность глобальных объявлений на самом деле полностью подчиняется всем капризам разработчика компилятора. Она может привести к нужному результату или сгореть синим пламенем.

// В файле file1.cpp 
Foo foo;
Foo* f = &foo;
// В файле file2.cpp
extern Foo* f;
Foo f1(*f);      // Используется конструктор копий


Если бы все это находилось в одном исходном файле, ситуация была бы нормальной. Со строкой Fоо* f = &foo; проблем не возникает, поскольку глобальные объекты одного исходного файла заведомо (хе-хе) инициализируются в порядке их определения. Другими словами, когда программа доберется до этой строки, объект foo уже будет сконструирован. Тем не менее, никто не гарантирует, что глобальные объекты в файле file1.cpp будут инициализированы раньше глобальных объектов в файле file2.срр. Если file2.cpp будет обрабатываться первым, f оказывается равным 0 (NULL на большинстве компьютеров), и при попытке получить по нему объект ваша программа героически умрет.

Лучший выход — сделать так, чтобы программа не рассчитывала на конкретный порядок инициализации файлов .срр. Для этого используется стандартный прием — в заголовочном файле .h определяется глобальный объект со статической переменной, содержащей количество
инициализированных файлов .срр. При переходе от 0 к 1 вызывается функция, которая инициализирует все глобальные объекты библиотечного файла .срр. При переходе от 1 к 0 все объекты этого файла уничтожаются.

// В файле Library.h 
class Library {
private:
static i nt c ount;
  static void OpenLibrary();
  static void CloseLibrary();
public:
Library();
~Library();
};
static Library LibraryDummy;
inline Library::Library()
{
  if (count++ == 0)
  OpenLibrary();
}
inline Library::~Library()
{
  if (--count == 0)
  CloseLibrary();
}
// В Library.cpp
int Library::count = 0;  // Д елается перед выполнением вычислений
int aGlobal;
40
Foo* aGlobalFoo;
void Library::OpenLibrary()
{
  aGlobal = 17;
  aGlobalFoo = new Foo;
}
void Library::CloseLibrary()
{
  aGlobal = 0;
delete a GlobalFoo;
aGlobalFoo = N ULL;
}


К этому нужно привыкнуть. А происходит следующее: файл .h компилируется со множеством других файлов .срр, один из которых - Library.cpp. Порядок инициализации глобальных объектов, встречающихся в этих файлах, предсказать невозможно. Тем не менее, каждый из них будет иметь
свою статическую копию LibraryDummy. При каждой инициализации файла .срр, в который включен файл Library.h, конструктор LibraryDummy увеличивает счетчик. При выходе из main() или при вызове exit() файлы .срр уничтожают глобальные объекты и уменьшают счетчик в деструкторе LibraryDummy. Конструктор и деструктор гарантируют, что OpenLibrary() и CloseLibrary() будут вызваны ровно один раз.

Этот прием приписывается многим разным программистам, но самый известный пример его использования встречается в библиотеке iostream. Там он инициализирует большие структуры данных, с которыми работает библиотека, ровно один раз и лишь тогда, когда это требуется.
Уважаемый посетитель, Вы зашли на сайт как незарегистрированный пользователь.
Мы рекомендуем Вам зарегистрироваться либо войти на сайт под своим именем.
Информация
Посетители, находящиеся в группе Гости, не могут оставлять комментарии к данной публикации.