Это обсуждалось в usenet некоторое время назад, когда я пытался ответить на другой вопрос по stackoverflow: Точка создания элементов статических данных . Я думаю, что стоит сократить контрольный пример и рассмотреть каждый сценарий отдельно, поэтому давайте сначала рассмотрим его более широко:
struct C { C(int n) { printf("%d\n", n); } };
template<int N>
struct A {
static C c;
};
template<int N>
C A<N>::c(N);
A<1> a; // implicit instantiation of A<1> and 2
A<2> b;
У вас есть определение статического шаблона элемента данных. Это еще не создает никаких элементов данных, из-за 14.7.1
:
"... в частности, инициализация (и любые связанные побочные эффекты) элемента статических данных не происходит, если только сам элемент статических данных не используется таким образом, который требует определения элемента статических данных. . "
Определение чего-либо (= сущности) необходимо, когда эта сущность «используется», согласно одному правилу определения, которое определяет это слово (в 3.2/2
). В частности, если все ссылки взяты из необоснованных шаблонов, членов шаблона или sizeof
выражений или аналогичных вещей, которые не «используют» сущность (поскольку они либо не оценивают ее, либо просто не существуют) тем не менее, как функции / функции-члены, которые сами используются), такой статический член данных не создается.
Неявное создание экземпляром 14.7.1/7
создания экземпляров объявлений статических членов-данных, то есть создание экземпляра любого шаблона, необходимого для обработки этого объявления. Однако он не будет создавать экземпляры определений, то есть инициализаторы не создаются, а конструкторы типа этого статического члена данных не определяются неявно (помечаются как используемые).
Это означает, что приведенный выше код пока ничего не выдаст. Давайте теперь вызовем неявные экземпляры статических элементов данных.
int main() {
A<1>::c; // reference them
A<2>::c;
}
Это приведет к тому, что два статических члена данных будут существовать, но вопрос - каков порядок инициализации? При простом прочтении можно подумать, что применимо 3.6.2/1
, что говорит (выделено мной):
"Объекты со статической продолжительностью хранения, определенной в области имен в одной и той же единице перевода и динамически инициализированной, должны быть инициализированы в том порядке, в котором их определение появляется в единице перевода."
Теперь, как сказано в сообщении usenet и объяснено в этом отчете о дефектах , эти статические члены-данные не определены в единице перевода, но они создаются в единице реализации , как объяснено в 2.1/1
:
Каждая переведенная единица перевода проверяется для составления списка необходимых экземпляров. [Примечание: это может включать экземпляры, которые были явно запрошены (14.7.2). ] Определения необходимых шаблонов находятся. Это зависит от реализации, должен ли быть доступен источник блоков перевода, содержащих эти определения. [Примечание: реализация может закодировать достаточную информацию в переведенную единицу перевода, чтобы гарантировать, что источник здесь не требуется. ] Все необходимые экземпляры выполняются для создания единиц реализации. [Примечание: они похожи на переведенные единицы перевода, но не содержат ссылок на необоснованные шаблоны и определения шаблонов. ] Программа плохо сформирована, если не удается создать экземпляр.
Точка создания экземпляра такого члена также не имеет большого значения, поскольку такая точка создания экземпляра является контекстной связью между экземпляром и его единицами перевода - она определяет объявления, которые видны (как указано в 14.6.4.1
, и каждый из этих пунктов должен придавать экземплярам тот же смысл, который указан в одном правиле определения в 3.2/5
, последний пункт).
Если мы хотим упорядоченную инициализацию, мы должны упорядочить ее, чтобы не связываться с экземплярами, а с явными объявлениями - это область явных специализаций, поскольку они на самом деле не отличаются от обычных объявлений. Фактически, C ++ 0x изменил свою формулировку 3.6.2
на следующую:
Динамическая инициализация нелокального объекта со статической продолжительностью хранения либо упорядочена, либо неупорядочена.
Определения явно специализированных членов класса статических данных имеют упорядоченную инициализацию. Другой
Статические члены-данные шаблона класса (то есть неявные или явно созданные экземпляры специализаций) имеют неупорядоченную инициализацию.
Это значит для вашего кода, что:
[1]
и [2]
прокомментировал: ссылки на статические элементы данных отсутствуют, поэтому существуют их определения (а также их объявления, поскольку нет необходимости в создании экземпляров) из B<int>
) не созданы. Побочного эффекта не наблюдается.
[1]
без комментариев: используется B<int>::getB()
, что само по себе использует B<int>::mB
, что требует наличия статического члена. Строка инициализируется до main (в любом случае до этого оператора, как часть инициализации нелокальных объектов). Ничто не использует B<int>::mInit
, поэтому он не создан, и поэтому объект B<int>::InitHelper
никогда не создается, что делает его конструктор неиспользуемым, что, в свою очередь, никогда не назначит что-то для B<int>::mB
: вы просто выведете пустую строку ,
[1]
и [2]
без комментариев: То, что это сработало для вас, является удачей (или наоборот :)). Как указано выше, для определенного порядка вызовов инициализации не требуется. Это может работать на VC ++, не работать на GCC и работать на Clang. Мы не знаем.
[1]
с комментариями, [2]
без комментариев: та же проблема - снова, оба статических члена данных используются : B<int>::mInit
используется B<int>::getHelper
, а создание экземпляра B<int>::mInit
приведет к созданию экземпляра его конструктора, который будет использовать B<int>::mB
- но для вашего компилятора порядок в этом конкретном прогоне отличается (неуказанное поведение не требуется для согласованности между различными прогонами): сначала инициализируется B<int>::mInit
, который будет работать с еще не построенным строковым объектом.