Что должно случиться со статическими переменными-членами класса шаблона с определением в файле .h - PullRequest
11 голосов
/ 18 августа 2011

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

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

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

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

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

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

Ответы [ 3 ]

20 голосов
/ 18 августа 2011

Начиная с N3290, 14.6:

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

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

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>также используется в других TU, отличных от того, который содержит явное создание экземпляра, поскольку тогда вы получите несколько определений.

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

// in the header
template <typename T> int Foo<T>::n = GetInitialValue<T>::value;  // definition + initialization
2 голосов
/ 15 апреля 2013

Это дополнение к отличному ответу @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;

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

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

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

1 голос
/ 30 марта 2013

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

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

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

...