Ответ 1
Использование счетчиков Schwartz (так называемый после Джерри Шварца, который разработал основы библиотеки IOStreams, поскольку теперь он находится в стандарте; обратите внимание, что он может нельзя обвинять многих нечетных вариантов, поскольку они были помечены на исходный проект) может привести к доступу к объектам до их создания. Наиболее очевидным сценарием является вызов функции во время построения глобального объекта, который вызывает в другую единицу перевода, используя свой собственный глобальный объект, построенный с помощью счетчика Шварца (я использую std::cout
как глобальный, защищенный счетчиком Шварца, чтобы сохранить пример короткий):
// file a.h
void a();
// file a.cpp
#include <iostream>
void a() { std::cout << "a()\n"; }
// file b.cpp
#include <a.h>
struct b { b() { a(); } } bobject;
Если глобальные объекты в файле b.cpp
создаются до тех, что находятся в файле a.cpp
, и если std::cout
создается с помощью счетчика Шварца с a.cpp
, который является первым экземпляром, этот код не будет выполнен. Есть, по крайней мере, две другие причины, по которым счетчики Шварца не работают особенно хорошо:
- При использовании для этого глобального объекта эти объекты заканчиваются дважды. Хотя это работает на практике, когда все делается правильно, я думаю, что это уродливо. Обход для этого состоит в том, чтобы использовать буфер
char
соответствующего размера для фактического определения объекта (обычно они искажаются именем как объект правильного типа). Однако в обоих случаях все беспорядочно. - Когда глобальные объекты, защищенные счетчиком Шварца, используются во многих единицах перевода (как это имеет место для
std::cout
), это может вызвать значительную задержку запуска: хорошо написанный код обычно не использует глобальную инициализацию но счетчик Schwartz должен запускать кусок кода для каждого из объектных файлов, которые необходимо загрузить.
Лично я пришел к выводу, что этот метод - отличная идея, но на практике это не работает. Вместо этого я использую три подхода:
- Не используйте глобальные объекты. Это делает эту дискуссию устаревшей и лучше всего работает в параллельном коде. Если глобальный ресурс абсолютно необходим, статический объект функции, возвращенный ссылкой и инициализированный с использованием
std::call_once()
, является гораздо лучшей альтернативой. - Размещение глобального объекта в соответствующем месте при связывании исполняемого файла (например, последний) заставляет его сначала инициализировать. Я экспериментировал с этим в прошлом и обратно, тогда я обнаружил, что могу разместить объектные файлы надлежащим образом во всех системах, о которых я заботился. Основной недостаток здесь заключается в том, что нет никаких гарантий, и все может измениться при переключении между версиями компилятора. Однако для стандартной библиотеки С++ это приемлемо (и я только заботился о глобальных объектах потока, когда я это делал).
- Поместите глобальные объекты в выделенную общую библиотеку: при загрузке разделяемой библиотеки выполняется ее код инициализации. Объекты в общей библиотеке становятся доступными только после завершения инициализации. Я обнаружил, что это работает надежно, но требует дополнительной библиотеки.