39
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
int
count;
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
aGlobalFoo;
aGlobalFoo
=
NULL;
}
К этому нужно привыкнуть. А происходит следующее: файл .h компилируется со множеством других
файлов .срр, один из которых - Library.cpp. Порядок инициализации глобальных объектов,
встречающихся в этих файлах, предсказать невозможно. Тем не менее, каждый из них будет иметь
свою статическую копию
LibraryDummy
. При каждой инициализации файла .срр, в который включен
файл Library.h, конструктор
LibraryDummy
увеличивает счетчик. При выходе из
main()
или при
вызове
exit()
файлы .срр уничтожают глобальные объекты и уменьшают счетчик в деструкторе
LibraryDummy
. Конструктор и деструктор гарантируют, что
OpenLibrary()
и
CloseLibrary()
будут вызваны ровно один раз.
Этот прием приписывается многим разным программистам, но самый известный пример его
использования встречается в библиотеке iostream. Там он инициализирует большие структуры данных,
с которыми работает библиотека, ровно один раз и лишь тогда, когда это требуется.
Деструкторы
Деструкторы вызываются каждый раз, когда стековый объект выходит из области действия (включая
анонимные экземпляры и временные объекты, создаваемые компилятором) или когда для
динамического объекта вызывается оператор
delete
. К деструкторам относится ряд малоизвестных
фактов.
Порядок вызова
Деструкторы гарантированно вызываются в порядке, обратном порядку вызова конструкторов. Это
означает, что сначала вызывается тело конструктора объекта, затем деструкторы переменных класса в
порядке, обратном порядку их перечисления в объявлении класса, и наконец деструкторы базовых
классов, начиная с последнего в списке наследования и кончая первым базовым первого базового и т.д.
Уничтожение глобальных объектов
Если от разговоров об инициализации глобальных объектов у вас закружилась голова, могу вас
обрадовать. Если разработчик вашего компилятора справился со своей работой, деструкторы
глобальных объектов гарантированно вызываются в порядке, точно обратном порядку вызова
конструкторов.
Глобальные объекты уничтожаются при выходе из области действия
main()
или при вызове
exit()
.
Невиртуальные деструкторы
C++ выбирает вызываемый деструктор по типу указателя на объект. Если указатель имеет тип
base*
(указатель на базовый класс), возникнут проблемы, если только деструктор класса не виртуален.
class Foo {
public:
~Foo();
};
Do'stlaringiz bilan baham: |