Читаем Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ полностью

Единица трансляции (translation unit) – это исходный код, который порождает отдельный объектный файл. Обычно это один исходный файл плюс все файлы, включенные в него директивой #include.

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

Рассмотрим пример. Предположим, у вас есть класс FileSystem, который делает файлы из Internet неотличимыми от локальных. Поскольку ваш класс представляет мир как единую файловую систему, вы могли бы создать в глобальной области действия или в пространстве имен соответствующий ей специальный объект:


class FileSystem { // из вашей библиотеки

public:

...

std::size_t numDisks const; // одна из многих функций-членов

...

};

extern FileSystem tfs; // объект для использования клиентами

// “tfs” = “the file system”


Класс FileSystem определенно не тривиален, поэтому использование объекта theFileSystem до того, как он будет сконструирован, приведет к катастрофическим последствиям.

Теперь предположим, что некий пользователь создает класс, описывающий каталоги файловой системы. Естественно, его класс будет использовать объект theFileSystem:


class Directory { // создан пользователем

public:

Directory( params );

...

};

Directory::Directory( params )

{

...

std::size_t disks = tfs.numDisks; // использование объекта tfs

...

}


Далее предположим, что пользователь решает создать отдельный глобальный объект класса Directory, представляющий каталог для временных файлов:


Directory tempDir( params ); // каталог для временных файлов


Теперь проблема порядка инициализации становится очевидной: если объект tfs не инициализирован раньше, чем tempDir, то конструктор tempDir попытается использовать tfs до его инициализации. Но tfs и tempDir были созданы разными людьми в разное время и находятся в разных исходных файлах – это нелокальные статические объекты, определенные в разных единицах трансляции. Как вы можете быть уверены, что tfs будет инициализирован раньше, чем tempDir?

Да никак! Еще раз повторю: относительный порядок инициализации нестатических локальных объектов, определенных в разных единицах трансляции, не определен. На то есть своя причина. Определить «правильный» порядок инициализации нелокальных статических объектов трудно. Очень трудно. Неразрешимо трудно. В наиболее общем случае – при наличии многих единиц трансляции и нелокальных статических объектов, сгенерированных путем неявной конкретизации шаблонов (которые и сами могут быть результатом неявной конкретизации других шаблонов) – не только невозможно определить правильный порядок инициализации, но обычно даже не стоит искать частные случаи, когда этот порядок в принципе определить можно.

К счастью, небольшое изменение в проекте программы позволяет полностью устранить эту проблему. Нужно лишь переместить каждый нелокальный статический объект в отдельную функцию, в которой он будет объявлен статическим. Эти функции возвращают ссылки на объекты, которые в них содержатся. Клиенты затем вызывают функции вместо непосредственного обращения к объектам. Другими словами, нелокальные статические объекты заменяются локальными статическими объектами (знакомые с паттернами проектирования легко узнают в этом описании типичную реализацию паттерна Singleton).

Перейти на страницу:
Нет соединения с сервером, попробуйте зайти чуть позже