c++ templates static

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