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

Статическая переменная внутри функции шаблона

В С++, если вы определяете эту функцию в 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. Это нормально? Я задаю этот вопрос, потому что я полагаюсь на эту "функцию" (разделяя статическую переменную), и я хочу быть уверенным, что это делает не только моя реализация.

4b9b3361

Ответ 1

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

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

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

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

Изменить

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

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

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

Имя, имеющее область пространства имен (3.3.6), имеет внутреннюю связь, если это имя [...] объекта, ссылки, функции или шаблона функции, которое явно объявлено static -- 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>) из разных единиц перевода, они будут ссылаться на те же функции, что означает, что ваши статические объекты будут одинаковыми в каждом случае,

Ответ 2

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

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

Ответ 3

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

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

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

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

Ответ 4

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

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

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

Ответ 5

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

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

Ответ 6

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

#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", и, следовательно, в вашем случае вы не видите никаких проблем.

Ответ 7

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