c++ - Variable estática de plantilla
templates static (2)
Es porque la definición del miembro de datos estáticos es en sí misma una plantilla. Permitir esto es necesario por el mismo motivo por el que se le permite tener una plantilla de función que no está en línea varias veces en un programa. Necesita la plantilla para generar la entidad resultante (por ejemplo, una función o un miembro de datos estáticos). Si no se le permitiera poner la definición de un miembro de datos estático, ¿cómo crearía la siguiente instancia?
template<typename T>
struct F {
static int const value;
};
template<typename T>
int const F<T>::value = sizeof(T);
No se sabe qué es T
: el Estándar dice que la definición fuera de la plantilla de clase es una definición de plantilla, en la que los parámetros se heredan de su propietario de plantilla de clase.
He hecho algunos experimentos con GCC. A continuación, tenemos una instanciación implícita de F<float>::value
, y una especialización explícita de F<char>::value
que debe definirse en un archivo .cpp para no causar errores de símbolos duplicados cuando se incluyen varias veces .
// 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() { }
La segunda unidad de traducción contiene solo otra instanciación implícita del mismo miembro de datos estáticos
template<typename T>
struct F {
static int value;
};
template<typename T>
int F<T>::value = sizeof(T);
int test1 = F<float>::value;
Esto es lo que obtenemos con GCC: convierte cada instancia implícita en un símbolo débil y lo pega en su propia sección aquí. Los símbolos débiles no causarán errores cuando existan múltiples en el momento del enlace. En su lugar, el vinculador elegirá una instancia y descarta las otras suponiendo que todas son iguales
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
Entonces, como podemos ver, F<float>::value
es un símbolo débil, lo que significa que el enlazador puede ver varios de estos en el momento del enlace. test
, main
y F<char>::value
son símbolos globales (no débiles). Al unir main1.o
y main2.o
, vemos en el mapa salida ( -Wl,-M
) lo siguiente
# (mangled name)
.data._ZN1FIfE5valueE
0x080497ac 0x4 main1.o
0x080497ac F<float>::value
Esto indica que en realidad cae todo, excepto una instancia.
No puedo entender por qué si definimos la variable estática de la clase habitual (sin plantilla) en el encabezado, tenemos un error de enlazador, pero en el caso de las plantillas, todo funciona bien y, además, tendremos una única instancia de variable estática entre todas las unidades de traducción :
Es el encabezado de la plantilla (template.h):
// template.h
template<typename T>
class Templ {
public:
static int templStatic;
};
template<typename T> Templ<T>::templStatic = 0;
Es la primera unidad que usa la plantilla (unit1.cpp)
// unit1.cpp
#include "template.h"
int method1() {
return Templ<void>::templStatic++;
}
Segunda unidad aquí (unit2.cpp):
// unit2.cpp
#include "template.h"
int method2() {
return Templ<void>::templStatic++;
}
Y, finalmente, 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;
}
Después de compilar, vincular y ejecutar este código, tendremos el siguiente resultado:
0
1
Entonces, ¿por qué en el caso de las plantillas todo funciona bien (y como se espera)? ¿Cómo manejan esto el compilador o el enlazador (podemos compilar cada archivo .cpp en una llamada separada del compilador, y luego vincularlos con caling to linker, para que el compilador y el enlazador no "vean" todos los archivos .cpp al mismo tiempo)?
PD: mi compilador: msvcpp 9 (pero también lo verificó en mingw)
Hay una solución, puedes crear una clase padre y poner la variable estática en ella, luego hacer que tu clase de plantilla la herede de forma privada, he aquí un ejemplo:
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;
}
La salida será:
Object 1 key is: 0
Object 2 key is: 1