Подтвердить что ты не робот

С++ Статичная членная инициализация (шаблонная забава внутри)

Для статической инициализации члена я использую вложенную вспомогательную структуру, которая отлично работает для не templated классов. Однако, если класс-оболочка параметризуется шаблоном, вложенный класс инициализации не создается, если вспомогательный объект не доступен в основном коде. Для иллюстрации, упрощенный пример (В моем случае мне нужно инициализировать вектор).

#include <string>
#include <iostream>

struct A
{
    struct InitHelper
    {
        InitHelper()
        {
            A::mA = "Hello, I'm A.";
        }
    };
    static std::string mA;
    static InitHelper mInit;

    static const std::string& getA(){ return mA; }
};
std::string A::mA;
A::InitHelper A::mInit;


template<class T>
struct B
{
    struct InitHelper
    {
        InitHelper()
        {
            B<T>::mB = "Hello, I'm B."; // [3]
        }
    };
    static std::string mB;
    static InitHelper mInit;

    static const std::string& getB() { return mB; }
    static InitHelper& getHelper(){ return mInit; }
};
template<class T>
std::string B<T>::mB; //[4]
template<class T>
typename B<T>::InitHelper B<T>::mInit;


int main(int argc, char* argv[])
{
    std::cout << "A = " << A::getA() << std::endl;

//    std::cout << "B = " << B<int>::getB() << std::endl; // [1]
//    B<int>::getHelper();    // [2]
}

С g++ 4.4.1:

  • [1] и [2] прокомментировали:

    A = Hello, I'm A.

    Работает по назначению

  • [1] раскомментирован:

    A = Hello, I'm A.
    B = 

    Я бы ожидал, что InitHelper инициализирует mB

  • [1] и [2] раскоментированы:
    A = Hello, I'm A.
    B = Hello, I'm B.
    Работает по назначению
  • [1] прокомментировал, [2] раскомментировал:
    Segfault на статической стадии инициализации в [3]

Таким образом, мой вопрос: это ошибка компилятора или ошибка, встречающаяся между монитором и стулом? И если это так: есть ли элегантное решение (т.е. Без явного вызова статического метода инициализации)?

Большое спасибо за любые предложения и комментарии.

Обновление I:
Это кажется желательным поведением (как определено в стандарте ISO/IEC С++ 2003, 14.7.1):

Если член шаблона класса или шаблон-член явно не создан или явно не специализирован, специализация этого элемента неявно создается, когда специализация ссылается в контексте, который требует определения члена; в частности, инициализация (и любые связанные с ней побочные эффекты) статического члена данных не происходит, если сам статический член данных не используется таким образом, чтобы требовалось определение члена статического данных.

4b9b3361

Ответ 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 во-первых, который будет работать с еще не построенным строковым объектом.

Ответ 2

Проблема в том, что исправления, которые вы даете для статических переменных-членов, также являются шаблонами.

template<class T>
std::string B<T>::mB;
template<class T>
typename B<T>::InitHelper B<T>::mInit;

Во время компиляции это фактически не определяет, так как T неизвестно. Это что-то вроде объявления класса или определения шаблона, компилятор не генерирует код или резервное хранилище, когда он его видит.

Определение происходит неявно позже, когда вы используете класс шаблона. Поскольку в случае segfaulting вы не используете B <int> :: mInit, он никогда не создается.

Решение будет явно определять необходимый член (без его инициализации): поместить где-нибудь исходный файл a

template<>
typename B<int>::InitHelper B<int>::mInit;

Это работает в основном так же, как явное определение класса шаблона.

Ответ 3

  • [1] несанкционированный случай: Все хорошо. static InitHelper B<int>::mInit не существует. Если член класса шаблона (struct) не используется, он не компилируется.

  • [1] и [2] раскованный случай: Все хорошо. B<int>::getHelper() используется static InitHelper B<int>::mInit и mInit.

  • [1] прокомментировал, [2] раскомментирован: он работает для меня в VS2008.