2009-10-12 16 views
48

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 sola instancia de variable estática entre todas las unidades de traducción:Variable estática de plantilla

es cabecera plantilla (template.h):

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

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

es primera unidad usando la plantilla (unit1.cpp)

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

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

Segunda unidad de él Re (unit2.cpp):

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

Y, por último, 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 compilling, la vinculación y ejecución de este código, habremos siguiente resultado:

0 
1 

Entonces, ¿por qué en el caso de las plantillas todo funciona bien (y como se esperaba)? ¿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)?

PS: Mi compilador: msvcpp 9 (pero comprobado en MinGW también)

+0

Sería más útil si nos mostrara el código ** que ** no funciona. – JesperE

+0

Supongo que el código que no funciona es el que define una variable en un encabezado que se incluye en más de un archivo (no externo), lo que da como resultado una colisión de nombres. – falstro

Respuesta

54

Es porque la definición del miembro de datos estático es en sí 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 permitiría poner la definición de un miembro de datos estáticos, ¿cómo crear una instancia de la siguiente

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

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

No se sabe lo que T es - la Norma dice la definición fuera de la plantilla de clase es una definición de plantilla , en el 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 sólo otra ejemplificació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; 

Aquí está lo que obtenemos con GCC - que hace que cada instancia implícita en unos símbolos débiles y se 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 enlazador elegirá un caso, y descarta las otras asumiendo todos ellos son los mismos

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 

Así como podemos ver F<float>::value es un símbolo débil 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). La vinculación de main1.o y main2.o juntos, nos vemos en la salida del mapa (-Wl,-M) la siguiente

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

Esto indica que en realidad ignora todo excepto un caso.

+0

Ok. Pero cómo el enlazador, que ve dos "plantilla Templ :: templStatic = 0;" las definiciones (en unit1.cpp y unit2.cpp) manejan esta situación? ¿Los archivos de objeto tienen alguna metainformación específica de C++, que le puede decir al vinculador, que una definición puede ser ignorada (y, como resultado, no tenemos un error de enlazador de "múltiples definiciones")? – cybevnm

+0

agregó algo de GCC –

1

Hay solución, puede crear una clase padre y poner la variable estática en el mismo, a continuación, hacer su clase de plantilla heredan 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; 
} 

de salida será:

Object 1 key is: 0 
Object 2 key is: 1 
Cuestiones relacionadas