Статическая переменная внутри шаблонной функции - PullRequest
25 голосов
/ 15 июня 2009

В C ++, если вы определите эту функцию в header.hpp

void incAndShow()
{
  static int myStaticVar = 0;
  std::cout << ++myStaticVar << " " << std::endl;
}

и вы включаете header.hpp как минимум в два файла .cpp. Тогда у вас будет multiple definition of incAndShow(). Что ожидается. Однако, если вы добавите шаблон в функцию

template <class T>
void incAndShow()
{
  static int myStaticVar = 0;
  std::cout << ++myStaticVar << " " << std::endl;
}

тогда у вас не будет никакой ошибки multiple definition of. Аналогично, два разных .cpp, вызывающих функцию с одним и тем же шаблоном (например, incAndShow<int>()), будут совместно использовать myStaticVar. Это нормально? Я задаю этот вопрос, потому что я полагаюсь на эту «особенность» (разделяя статическую переменную), и я хочу быть уверенным, что это делает не только моя реализация.

Ответы [ 7 ]

30 голосов
/ 15 июня 2009

Вы можете положиться на это. ODR (одно определение правила) говорит в 3.2/5 в стандарте, где D обозначает шаблон нестатической функции (рукописный шрифт мной)

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

Из последних четырех требований два наиболее важных примерно равны

  • каждое определение D должно состоять из одинаковой последовательности токенов
  • имена в каждом определении должны относиться к одним и тем же вещам («сущностям»)

Редактировать

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

Именно здесь linkage включается. Если имя специализации шаблона функции (которая является функцией) имеет внешнюю связь (3.5/4), то имя, относящееся к такой специализации, относится к та же функция. Для шаблона, который был объявлен статическим, функции, созданные из него, имеют внутреннюю связь из-за

Объекты, созданные из шаблона с внутренней связью, отличаются от всех объектов, созданных в других единицах перевода. -- 14/4

Имя, имеющее область пространства имен (3.3.6), имеет внутреннюю связь, если это имя [...] объекта, ссылки, функции или шаблона функции, которые явно объявлены статическими -- 3.5/3

Если шаблон функции не был объявлен с использованием static, то он имеет внешнюю связь (что, кстати, также является причиной того, что мы вообще должны следовать ODR. В противном случае D не будет определяться множественно совсем!). Это может быть получено из 14/4 (вместе с 3.5/3)

Шаблон функции, не являющейся членом, может иметь внутреннюю связь; любое другое имя шаблона должно иметь внешнюю связь. -- 14/4.

Наконец, мы приходим к выводу, что специализация шаблона функции, сгенерированная из шаблона функции с внешней связью, сама имеет внешнюю связь на 3.5/4:

Имя, имеющее область имен, имеет внешнюю связь, если это имя [...] функции, если оно не имеет внутренней связи -- 3.5/4

И когда это имеет внутреннюю связь, было объяснено 3.5/3 для функций, предоставляемых явными специализациями, и 14/4 для сгенерированных специализаций (создания шаблона). Поскольку имя вашего шаблона имеет внешнюю связь, все ваши специализации имеют внешнюю связь: если вы используете их имя (incAndShow<T>) из разных единиц перевода, они будут ссылаться на одни и те же функции, что означает, что ваши статические объекты будут одинаковыми в каждом случае ,

6 голосов
/ 15 июня 2009

Просто так я понимаю твой вопрос. Вы спрашиваете, нормально ли для каждой версии шаблонной функции иметь собственный экземпляр myStaticVar. (например: incAndShow<int> против intAndShow<float> Ответ - да.

Ваш другой вопрос: если два файла содержат заголовок, содержащий функцию шаблона, будут ли они по-прежнему использовать статическую переменную для данного T. Я бы сказал, да.

2 голосов
/ 15 июня 2009

Разница при создании шаблона функции заключается в том, что он имеет внешнюю связь. То же самое incAndShow будет доступно из всех модулей перевода.

Перефразируя из стандартного рабочего проекта C ++ N2798 (2008-10-04): 14 часть 4: шаблон функции, не являющейся членом, может иметь внутреннюю связь, другие всегда имеют внешнюю связь. 14.8, пункт 2: каждая специализация будет иметь свою собственную копию статической переменной.

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

Кроме того, это может привести к странным ситуациям, если вы по-разному определяете incAndShow в разных единицах перевода. Например, если вы определите его для приращения в одном файле и уменьшения в другом файле (без указания внутренней связи путем помещения функции в безымянное пространство имен), то в итоге обе будут использовать одну и ту же функцию, которая будет эффективно выбираться случайным образом во время компиляции. (в g ++ это зависит от порядка, в котором объектные файлы передаются в командной строке).

0 голосов
/ 22 июля 2009

Возьмите этот пример, который показывает, что поведение абсолютно ожидаемое:

#include <iostream>

template <class T> class Some
{
public:
   static int stat;
};

template<class T>
int Some<T>::stat = 10;

void main()
{
   Some<int>::stat = 5;
   std::cout << Some<int>::stat   << std::endl;
   std::cout << Some<char>::stat  << std::endl;
   std::cout << Some<float>::stat << std::endl;
   std::cout << Some<long>::stat  << std::endl;
}

Вы получаете: 5 10 10 10 10

Выше показано, что изменение статической переменной относится только к типу "int", и, следовательно, в вашем случае вы не видите никаких проблем.

0 голосов
/ 15 июня 2009

Да, это «нормально», но то, что вы пытаетесь достичь с помощью этой «функции», может быть сомнительным. Попробуйте объяснить, почему вы хотите использовать локальную статическую переменную, может быть, мы сможем придумать более чистый способ сделать это.

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

0 голосов
/ 15 июня 2009
  • шаблоны будут фактически превращены в код только после того, как они будут созданы (т.е. использованы)
  • заголовки не должны использоваться для кода реализации, а только для объявлений
0 голосов
/ 15 июня 2009

Шаблоны создаются по мере необходимости, что означает, что компилятор (в данном случае и компоновщик?) Будет следить за тем, чтобы у вас не было нескольких экземпляров одного и того же шаблона, а также только тех экземпляров шаблонов, которые вам нужны - в вашем случае создается только incAndShow<int>() и ничего больше (в противном случае компилятору придется пытаться создавать экземпляры для каждого типа, который не имеет смысла).

Таким образом, я предполагаю, что те же методы, которые он использует, чтобы выяснить, для какого типа создается экземпляр шаблона, предотвращают его создание дважды для одного и того же типа, например. только один экземпляр incAndShow<int>()

Это отличается от кода не шаблона.

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