Variável estática de modelo

Eu não consigo entender, porque se nós definimos a variável estática da class usual (não-modelo) no header, temos erro de linker, mas no caso de templates tudo funciona bem e além disso teremos instância única de variável estática entre todas as unidades de tradução :

É o header do template (template.h):

// template.h template class Templ { public: static int templStatic; }; template Templ::templStatic = 0; 

É a primeira unidade usando template (unit1.cpp)

 // unit1.cpp #include "template.h" int method1() { return Templ::templStatic++; } 

Segunda unidade aqui (unit2.cpp):

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

E, finalmente, main.cpp:

 // main.cpp #include  int method1(); int method2(); int main(int argc, char** argv) { std::cout << method1() << std::endl; std::cout << method2() << std::endl; } 

Após compilar, vincular e executar este código, teremos a seguinte saída:

 0 1 

Então, por que, no caso de modelos, tudo funciona bem (e como esperado)? Como o compilador ou o linker lidam com isso (podemos compilar cada arquivo .cpp em uma chamada separada do compilador e, em seguida, vinculá-los à chamada ao linker, para que o compilador eo vinculador não “vejam” todos os arquivos .cpp ao mesmo tempo)?

PS: Meu compilador: msvcpp 9 (mas verificado no mingw também)

É porque a definição do membro de dados estáticos é em si um modelo. Permitir isso é necessário pelo mesmo motivo que você tem permissão para ter um modelo de function que não esteja embutido várias vezes em um programa. Você precisa do modelo para gerar a entidade resultante (digamos, uma function ou um membro de dados estático). Se você não teria permissão para colocar a definição de um membro de dados estático, como você instanciaria o seguinte

 template struct F { static int const value; }; template int const F::value = sizeof(T); 

Não se sabe o que é T – o padrão diz que a definição fora do modelo de class é uma definição de modelo, na qual os parâmetros são herdados de seu proprietário de modelo de class.


Eu fiz alguns experimentos com o GCC. A seguir, temos uma instanciação implícita de F::value e uma especialização explícita de F::value que deve ser definida em um arquivo .cpp para não causar erros de símbolo duplicados quando incluídos várias vezes .

 // Translation Unit 1 template struct F { static int value; }; template int F::value = sizeof(T); // this would belong into a .cpp file template<> int F::value = 2; // this implicitly instantiates F::value int test = F::value; int main() { } 

A segunda unidade de tradução contém apenas outra instanciação implícita do mesmo membro de dados estáticos

 template struct F { static int value; }; template int F::value = sizeof(T); int test1 = F::value; 

Aqui está o que obtemos com o GCC – torna cada instanciação implícita em um símbolo fraco e coloca-o em sua própria seção aqui. Símbolos fracos não causarão erros quando houver vários deles no momento do link. Em vez disso, o vinculador escolherá uma instância e descartará as outras, assumindo que todas são iguais.

 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 ld .data._ZN1FIfE5valueE 00000000 .data._ZN1FIfE5valueE 00000028 l F .text 0000001c global constructors keyed to _ZN1FIcE5valueE 00000000 g O .data 00000004 F::value 00000000 g O .bss 00000004 test 00000000 g F .text 0000000a main 00000000 w O .data._ZN1FIfE5valueE 00000004 F::value 

Então, como podemos ver F::value é um símbolo fraco, o que significa que o linker pode ver múltiplos destes no momento do link. test , main e F::value são símbolos globais (não-fracos). Ligando main1.o e main2.o juntos, vemos na saída do mapa ( -Wl,-M ) os seguintes

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

Isso indica que, na verdade, cai tudo, exceto uma instância.

Não há solução, você pode criar uma class pai e colocar a variável estática nela, então faça a sua class de modelo herdá-la de forma privada, aqui está um exemplo:

 class Parent { protected: static long count; }; long Parent::count = 0; template class TemplateClass: private Parent { private: int mKey; public: TemplateClass():mKey(count++){} long getKey(){return mKey;} } int main() { TemplateClass obj1; TemplateClass obj2; std::cout<<"Object 1 key is: "< 

Saída será:

 Object 1 key is: 0 Object 2 key is: 1