Инициализация статического члена C ++ (забавный шаблон внутри) - PullRequest
38 голосов
/ 30 ноября 2009

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

#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 C ++ 2003, 14.7.1):

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

Ответы [ 3 ]

37 голосов
/ 01 декабря 2009

Это обсуждалось в 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, который будет работать с еще не построенным строковым объектом.
4 голосов
/ 30 ноября 2009

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

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

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

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

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

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

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...