c++ - template - Usando variable estática junto con plantillas
static c++ para que sirve (5)
¿Esperabas "F1: 1 Main: 1"? Instaló MyClass<int>
en dos unidades de traducción separadas (es decir, dos archivos de objetos), y el vinculador vio que había una creación de instancias de plantilla duplicada, por lo que descartó la creación de instancias que estaba en el archivo de objetos de f1
.
¿Está pasando /OPT:ICF
o /OPT:REF
al enlazador VC6? Eso podría estar relacionado con la eliminación de la instanciación de la plantilla duplicada (o no; las ejemplificaciones de la plantilla duplicada pueden ser un caso especial, en comparación con las funciones duplicadas ordinarias). GCC parece hacer algo similar en algunas plataformas.
De todos modos, no confiaría en que este comportamiento sea consistente en todos los compiladores. Además, cambiar el orden de los archivos de objetos en la línea de comando del vinculador puede afectar a qué instanciación se descarta.
Tengo una clase de plantilla definida en un archivo de encabezado como este. Aquí también he definido una variable estática:
#ifndef TEST1_H_
#define TEST1_H_
void f1();
static int count;
template <class T>
class MyClass
{
public:
void f()
{
++count;
}
};
#endif
Y he definido la función main () en un archivo cpp diferente como este:
int main(int argc, char* argv[])
{
MyClass<int> a;
a.f();
f1();
cout<<"Main:" << count << "/n";
return 0;
}
He implementado la función f1 () en un archivo cpp diferente como este:
void f1()
{
MyClass<int> a;
a.f();
cout<<"F1: " <<count <<"/n";
}
Cuando compilé esto usando VC6, obtuve la salida como "F1: 0 Main: 2". ¿Cómo es esto posible? Además, en general, ¿cómo debo manejar si quiero usar variables estáticas junto con plantillas?
Creo que esto es en realidad un comportamiento indefinido .
Según C ++ 14 [basic.def.odr] / 6:
Puede haber más de una definición de una función miembro de una plantilla [...] de clase en un programa, siempre que cada definición aparezca en una unidad de traducción diferente, y siempre que las definiciones cumplan con los siguientes requisitos. Dada una entidad llamada
D
definida en más de una unidad de traducción, entonces
- cada definición de D constará de la misma secuencia de fichas; y
- en cada definición de D, los nombres correspondientes, consultados según 3.4, se referirán a una entidad definida dentro de la definición de D, o se referirán a la misma entidad, después de la resolución de sobrecarga (13.3) y después de la coincidencia de la especialización de plantilla parcial (14.8 .3), excepto que un nombre puede referirse a un objeto const no volátil con un enlace interno o sin enlace si el objeto tiene el mismo tipo literal en todas las definiciones de D, y el objeto se inicializa con una expresión constante (5.19), y el objeto no se usa con frecuencia, y el objeto tiene el mismo valor en todas las definiciones de D; [...]
El problema es que en el primer archivo .cpp
, el count
nombres dentro de f1
refiere a un objeto diferente que el count
nombres dentro de f1
en el segundo archivo .cpp
, violando así la condición de que los nombres correspondientes deben referirse a la misma entidad.
Son objetos diferentes debido al especificador static
que dice que cada unidad de traducción obtiene su propio objeto con ese nombre.
Hay otra solución, puede crear una clase primaria compartida y poner esta variable estática en ella, luego hacer que su clase de plantilla la herede de forma privada, aquí hay 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
Obtendrá dos copias de la misma variable porque ha declarado una variable estática en un archivo de encabezado. Cuando declara una variable global static
esta manera, está diciendo que es local a la unidad de compilación (el archivo .o
). Dado que incluye el encabezado en dos unidades de compilación, obtiene dos copias del count
.
Creo que lo que realmente desea es una variable de miembro de plantilla estática asociada con cada instancia de la clase de plantilla. Se vería así:
template <class T>
class MyClass
{
// static member declaration
static int count;
...
};
// static member definition
template<class T> int MyClass<T>::count = 0;
Esto te dará una cuenta para cada instanciación de tu plantilla. Es decir, tendrás una cuenta para MyClass<int>
, MyClass<foo>
, MyClass<bar>
, etc. f1()
ahora se vería así:
void f1() {
MyClass<int> a;
a.f();
cout<<"F1: " << MyClass<int>::count <<"/n";
}
Si desea un recuento para todas las instancias de MyClass (independientemente de sus parámetros de plantilla), necesita usar una variable global .
Sin embargo, es probable que no desee una variable global directamente porque corre el riesgo de usarla antes de que se inicialice. Puede solucionar esto haciendo un método estático global que devuelva una referencia a su cuenta:
int& my_count() {
static int count = 0;
return count;
}
Entonces accediendo desde tu clase de esta manera:
void f() {
++my_count();
}
Esto asegurará que el conteo se inicie antes de usarlo, independientemente de la unidad de compilación desde la que acceda. Consulte las preguntas frecuentes de C ++ sobre el orden de inicialización estática para obtener más detalles.
Poner la declaración estática en un archivo de encabezado hará que cada archivo .cpp obtenga su propia versión de la variable. Así que las dos declaraciones de cout están imprimiendo diferentes variables.