Шаблон статической переменной - PullRequest
56 голосов
/ 12 октября 2009

Я не могу понять, почему, если мы определяем статическую переменную обычного (не шаблонного) класса в заголовке, у нас есть ошибка компоновщика, но в случае шаблонов все работает нормально, и, кроме того, у нас будет один экземпляр статической переменной среди все переводческие единицы:

Это заголовок шаблона (template.h):

// template.h
template<typename T>
class Templ {
public:
  static int templStatic;
};

template<typename T> Templ<T>::templStatic = 0;

Это первый модуль, использующий шаблон (unit1.cpp)

// unit1.cpp
#include "template.h"

int method1() {
  return Templ<void>::templStatic++;
}

Второй блок здесь (unit2.cpp):

// unit2.cpp
#include "template.h"
int method2() {
  return Templ<void>::templStatic++;
}

И, наконец, main.cpp:

// main.cpp
#include <iostream>
int method1();
int method2();

int main(int argc, char** argv) {
  std::cout << method1() << std::endl;
  std::cout << method2() << std::endl;
}

После компиляции, компоновки и выполнения этого кода у нас будет следующий вывод:

0
1

Так почему же в случае с шаблонами все работает нормально (и как положено)? Как компилятор или компоновщик справляются с этим (мы можем скомпилировать каждый файл .cpp в отдельном вызове компилятора, а затем связать их с помощью вызова компоновщика, чтобы компилятор и компоновщик не «видели» все файлы .cpp одновременно)?

PS: Мой компилятор: msvcpp 9 (но также проверен на mingw)

Ответы [ 3 ]

61 голосов
/ 12 октября 2009

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

template<typename T>
struct F {
  static int const value;
};

template<typename T>
int const F<T>::value = sizeof(T);

Неизвестно, что такое T - Стандарт говорит, что определение вне шаблона класса является определением шаблона, в котором параметры наследуются от его владельца шаблона класса.


Я провел некоторый эксперимент с GCC. Далее мы имеем одно неявное создание экземпляра F<float>::value и одну явную специализацию F<char>::value, которая должна быть определена в файле .cpp, чтобы не вызывать дублированных ошибок символов при многократном включении.

// Translation Unit 1
template<typename T>
struct F {
  static int value; 
};

template<typename T>
int F<T>::value = sizeof(T);

// this would belong into a .cpp file
template<> int F<char>::value = 2;

// this implicitly instantiates F<float>::value
int test = F<float>::value;

int main() { }

Второй блок перевода содержит просто еще одно неявное создание того же элемента статических данных

template<typename T>
struct F {
  static int value; 
};

template<typename T>
int F<T>::value = sizeof(T);

int test1 = F<float>::value;

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

objdump -Ct main1.o # =>
# cut down to the important ones
00000000 l    df *ABS*  00000000 main1.cpp
0000000a l     F .text  0000001e __static_initialization_and_destruction_0(int, int)
00000000 l    d  .data._ZN1FIfE5valueE  00000000 .data._ZN1FIfE5valueE
00000028 l     F .text  0000001c global constructors keyed to _ZN1FIcE5valueE
00000000 g     O .data  00000004 F<char>::value
00000000 g     O .bss   00000004 test
00000000 g     F .text  0000000a main
00000000  w    O .data._ZN1FIfE5valueE  00000004 F<float>::value

Итак, как мы видим, F<float>::value - это слабый символ, который означает, что компоновщик может видеть несколько из них во время соединения. test, main и F<char>::value являются глобальными (не слабыми) символами. Связав main1.o и main2.o вместе, мы видим в выводе карты (-Wl,-M) следующее

# (mangled name)
.data._ZN1FIfE5valueE
    0x080497ac        0x4 main1.o                                             
    0x080497ac                F<float>::value

Это означает, что на самом деле он отбрасывает все, кроме одного экземпляра.

2 голосов
/ 18 декабря 2016

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

class Parent
{
protected: 
    static long count;
};

long Parent::count = 0;

template<typename T>
class TemplateClass: private Parent
{
private: 
    int mKey;
public:
    TemplateClass():mKey(count++){}
    long getKey(){return mKey;}
}

int main()
{
    TemplateClass<int> obj1;
    TemplateClass<double> obj2;

    std::cout<<"Object 1 key is: "<<obj1.getKey()<<std::endl;
    std::cout<<"Object 2 key is: "<<obj2.getKey()<<std::endl;

    return 0;
}

Вывод будет:

Object 1 key is: 0 
Object 2 key is: 1
0 голосов
/ 10 февраля 2019

Это потому, что код шаблона не является исходным кодом; это инструкции по написанию исходного кода.

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

Когда компилятор видит

template<class T> Templ{...};

ничего не делает, кроме как отмечает, что шаблон существует. Что касается этого, то нет никакого исходного кода, связанного с Templ. Первый раз, когда вы на самом деле ссылаетесь на

Templ<int> Instance

компилятор просматривает весь код шаблона <>, связанный с Templ, и использует его для создания файла .h и .cpp (который существует только на время компиляции). Эти файлы могут выглядеть так:

Temple_int.h
class Templ_int{
  public:
  static int templStatic;
};

Templ_int.cpp
#include "Templ_int.h"
Templ_int::templStatic = 0;

И каждый

Templ<int>

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

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