Что должно произойти с переменными статического члена шаблона класса с определением в файле .h

Если определение класса шаблона содержит статическую переменную-член, которая зависит от типа шаблона, я не уверен, что должно быть надежным поведением?

В моем случае желательно поместить определение этого статического члена в тот же файл .h, что и определение класса, поскольку

  • Я хочу, чтобы класс был общим для многих типов данных шаблонов, которых я не сейчас знать.
  • Мне нужен только один экземпляр статического члена для совместного использования в моей программе для каждого типа шаблона. (один для всех MyClass<int> и один для всех MyClass<double> и т.д.

Я могу быть самым кратким, говоря, что код, указанный по этой ссылке ведет себя точно так, как я хочу, когда скомпилирован с gcc 4.3. Это поведение соответствует стандарту С++, поэтому я могу полагаться на него при использовании других компиляторов?

Эта ссылка не является моим кодом, но встречный пример, отправленный CodeMedic в обсуждение здесь. Я нашел несколько других дебатов вроде один, но ничего не считаю окончательным.

Я думаю, что компоновщик объединяет множество найденных определений (в примере a.o и b.o). Является ли это обязательным/надежным линкером?

Ответ 1

Из N3290, 14.6:

A [...] элемент статических данных шаблона класса должен быть определен в каждая единица перевода, в которой она неявно создается экземпляром [...], если соответствующая специализация явно не создана [...].

Как правило, вы помещаете определение статического члена в файл заголовка вместе с определением класса шаблона:

template <typename T>
class Foo
{
  static int n;                       // declaration
};

template <typename T> int Foo<T>::n;  // definition

Чтобы расширить концессию: если вы планируете использовать явные экземпляры в вашем коде, например:

template <> int Foo<int>::n = 12;

тогда вы не должны ставить шаблонное определение в заголовок, если Foo<int> также используется в других ТУ, отличных от тех, которые содержат явное инстанцирование, так как тогда вы получите несколько определений.

Однако, если вам нужно установить начальное значение для всех возможных параметров без использования явного инстанцирования, вы должны поместить его в заголовок, например. с TMP:

// in the header
template <typename T> int Foo<T>::n = GetInitialValue<T>::value;  // definition + initialization

Ответ 2

Это полностью дополнение к замечательному ответу @Kerrek SB. Я бы добавил его в качестве комментария, но их уже много, поэтому новые комментарии скрыты по умолчанию.

Итак, его и другие примеры, которые я видел, являются "легкими" в том смысле, что тип статической переменной-члена известен заранее. Это легко, потому что компилятор, например, знает размер хранилища для любого экземпляра шаблона, поэтому можно подумать, что компилятор может использовать фанк-манипуляцию, определение выходной переменной один раз и выгрузить остальную часть в компоновщик, и это может даже работать.

Но это немного удивительно, что он работает, когда тип статического члена зависит от параметра шаблона. Например, следующие работы:

template <typename width = uint32_t>
class Ticks : public ITimer< width, Ticks<width> >
{
protected:
    volatile static width ticks;
}
template <typename width> volatile width Ticks<width>::ticks;

(Обратите внимание, что для явного экземпляра static var не требуется (или разрешено) спецификация по умолчанию для "width" ).

Итак, это приносит больше мыслей, что компилятор С++ должен выполнять довольно много обработки - в частности, для создания экземпляра шаблона необходим не только сам шаблон, но он также должен собирать все [статические элементы] явных экземпляров (можно только задаться вопросом, почему они были сделаны отдельными синтаксическими конструкциями, а не чем-то, что должно быть указано в классе шаблона).

Что касается реализации этого на уровне компоновщика, для GNU binutils его "общие символы": http://sourceware.org/binutils/docs/as/Comm.html#Comm. (Для Microsoft toolchains он называется COMDAT, как говорит другой ответ).

Ответ 3

Компилятор обрабатывает такие случаи почти точно так же, как и для статических членов класса без шаблона, с применением __ declspec (selectany), например:

class X {
public:
X(int i){};
};
__declspec(selectany) X x(1);//works in msvc, for gcc use __attribute__((weak))

И как msdn говорит: "При времени ссылки, если видны несколько определений COMDAT, компоновщик выбирает один и отбрасывает остальное... Для динамически инициализированных глобальных объектов selectany также отменит код инициализации объекта без ссылок."