Ответ 1
Это обсуждалось в 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). ] Определены требуемые шаблоны. Определяется реализация, требуется ли источник блоков перевода, содержащих эти определения. [Примечание: реализация может кодировать достаточную информацию в переведенную единицу перевода, чтобы гарантировать, что источник здесь не требуется. ] Все необходимые экземпляры выполняются для создания единиц экземпляра. [Примечание: они аналогичны переведенным единицам перевода, но не содержат ссылок на неинсталлированные шаблоны и определения шаблонов. ] Программа плохо сформирована, если какой-либо экземпляр не работает.
Точка Instantiation такого члена также не имеет большого значения, поскольку такая точка инстанцирования является контекстной связью между экземпляром и его единицами перевода - он определяет видимые объявления (как указано в 14.6.4.1
, и каждая из этих точек инстанцирования должна давать экземпляры того же значения, которые указаны в одном правиле определения в 3.2/5
, последней пуле).
Если нам нужна упорядоченная инициализация, мы должны устроить так, чтобы мы не связывались с экземплярами, но с явными объявлениями - это область явных специализаций, так как они не отличаются от обычных деклараций. Фактически, С++ 0x изменил свою формулировку 3.6.2
на следующее:
Динамическая инициализация нелокального объекта со статической продолжительностью хранения либо упорядоченная, либо неупорядоченная. Определения явно специализированных классов статических элементов шаблона шаблона заказали инициализацию. Другие кластерные статические элементы данных (т.е. неявно или явно созданные экземпляры) имеют неупорядоченную инициализацию.
Это означает, что ваш код:
-
[1]
и[2]
прокомментировал: ссылки на статические члены данных отсутствуют, поэтому их определения (а также не их декларации, так как нет необходимости в создании экземпляраB<int>
) не создаются. Отсутствует побочный эффект. -
[1]
используется без комментирования:B<int>::getB()
, который сам по себе используетB<int>::mB
, для чего требуется, чтобы статический член существовал. Строка инициализируется до основного (в любом случае перед этим оператором, как часть инициализации нелокальных объектов). Ничто не используетB<int>::mInit
, поэтому оно не создается, поэтому объектB<int>::InitHelper
не создается, поэтому его конструктор не используется, что в свою очередь никогда не будет присваивать что-тоB<int>::mB
: вы просто выведете пустую строку, -
[1]
и[2]
раскомментировано: это сработало для вас - удача (или наоборот:)). Нет требования для определенного порядка вызовов инициализации, как объяснялось выше. Он может работать на VС++, не работать в GCC и работать над clang. Мы не знаем. -
[1]
прокомментировано,[2]
раскомментировано: та же проблема: снова используются оба элемента статических данных:B<int>::mInit
используетсяB<int>::getHelper
и экземплярB<int>::mInit
приведет к созданию его конструктора, который будет использоватьB<int>::mB
, но для вашего компилятора порядок отличается в этом конкретном прогоне (неопределенное поведение не обязательно должно быть согласованным между разными запусками): оно инициализируетB<int>::mInit
во-первых, который будет работать с еще не построенным строковым объектом.