2009-11-30 11 views
37

Para la inicialización de miembros estáticos, uso una estructura de ayuda anidada, que funciona bien para las clases sin plantillas. Sin embargo, si la clase adjunta se parametriza mediante una plantilla, la clase de inicialización anidada no se crea una instancia, si no se accede al objeto auxiliar en el código principal. Para ilustrar, un ejemplo simplificado (en mi caso, necesito inicializar un vector).C++ Inicialización de miembros estáticos (diversión de la plantilla en el interior)

#include <string> 
#include <iostream> 

struct A 
{ 
    struct InitHelper 
    { 
     InitHelper() 
     { 
      A::mA = "Hello, I'm A."; 
     } 
    }; 
    static std::string mA; 
    static InitHelper mInit; 

    static const std::string& getA(){ return mA; } 
}; 
std::string A::mA; 
A::InitHelper A::mInit; 


template<class T> 
struct B 
{ 
    struct InitHelper 
    { 
     InitHelper() 
     { 
      B<T>::mB = "Hello, I'm B."; // [3] 
     } 
    }; 
    static std::string mB; 
    static InitHelper mInit; 

    static const std::string& getB() { return mB; } 
    static InitHelper& getHelper(){ return mInit; } 
}; 
template<class T> 
std::string B<T>::mB; //[4] 
template<class T> 
typename B<T>::InitHelper B<T>::mInit; 


int main(int argc, char* argv[]) 
{ 
    std::cout << "A = " << A::getA() << std::endl; 

// std::cout << "B = " << B<int>::getB() << std::endl; // [1] 
// B<int>::getHelper(); // [2] 
} 

Con g ++ 4.4.1:

  • [1] y [2] comentó:

    A = Hello, I'm A.

    funciona como está previsto

  • [1] uncommented:

    A = Hello, I'm A. 
    B =

    que era de esperar, que la InitHelper inicializa mB

  • [1] y [2] sin comentar:
    A = Hello, I'm A. 
    B = Hello, I'm B.
    funciona como está previsto
  • [1] comentó, [2] sin comentar:
    segfault en la etapa de inicialización estática en [3]

Así que mi pregunta: ¿Es esto un error del compilador o es el error sentado entre el monitor y la silla? Y si este es el caso: ¿hay una solución elegante (es decir, sin llamar explícitamente a un método de inicialización estático)?

Actualización I:
Esto parece ser un comportamiento deseado (como se define en la norma ISO/IEC estándar de C++ 2003, 14.7.1):

menos que un miembro de una plantilla de clase o una la plantilla de miembro ha sido explícitamente instanciada o explícitamente especializada, la especialización del miembro se instancia de forma implícita cuando se hace referencia a la especialización en un contexto que requiere que exista la definición de miembro; en particular, la inicialización (y cualquier efecto secundario asociado) de un miembro de datos estáticos no se produce a menos que el miembro de datos estáticos se use de una manera que requiera la definición del miembro de datos estáticos.

+0

Visual Studio 2008 tiene el mismo comportamiento (-segfault en la inicialización estática) –

+0

¿Por qué no acaba de escribir std :: string B :: mB = "Hola, soy B."? –

+0

Ok, veo que en el caso real que necesita para inializar un vector, lo siento. –

Respuesta

36

Esto fue discutido en usenet hace algún tiempo, mientras intentaba responder a otra pregunta en stackoverflow: Point of Instantiation of Static Data Members. Creo que vale la pena reducir la prueba de los casos, y teniendo en cuenta cada situación de forma aislada, por lo que vamos a ver de primera más general:


struct C { C(int n) { printf("%d\n", n); } }; 

template<int N> 
struct A { 
    static C c; 
}; 

template<int N> 
C A<N>::c(N); 

A<1> a; // implicit instantiation of A<1> and 2 
A<2> b; 

Usted tiene la definición de una plantilla de miembro de datos estáticos. Esto sin embargo no crea ningún miembro de datos, debido a 14.7.1:

" ..., en particular, no se produce la inicialización (y los efectos secundarios asociados) de un miembro de datos estáticos a menos que el miembro de datos estático es se usa de una manera que requiere la definición del miembro de datos estáticos para existir ".

La definición de algo (= entidad) es necesaria cuando la entidad es "usado", de acuerdo con la regla que define una definición de esa palabra (al 3.2/2). En particular, si todas las referencias provienen de plantillas desinstaladas, miembros de una plantilla o expresiones sizeof o cosas similares que no "usan" la entidad (ya que no están potencialmente evaluándola, o simplemente no existen todavía como funciones/funciones de miembros que se usan por sí mismas), como un miembro de datos estáticos no se crea una instancia.

Una instanciación implícita por 14.7.1/7 instancia declaraciones de miembros de datos estáticos, es decir, instanciará cualquier plantilla necesaria para procesar esa declaración. Sin embargo, no creará instancias de definiciones, es decir, los inicializadores no se instanciarán y los constructores del tipo de ese miembro de datos estáticos no se definirán implícitamente (marcados como utilizados).

Eso significa que el código anterior no dará salida todavía. Vamos a crear instancias implícitas de los miembros de datos estáticos ahora.

int main() { 
    A<1>::c; // reference them 
    A<2>::c; 
} 

Esto provocará que existan los dos miembros de datos estáticos, pero la pregunta es - cómo es el orden de inicialización? En una simple lectura, se podría pensar que se aplica 3.6.2/1, que dice (el subrayado por mí):

"Objetos con una duración de almacenamiento estático se define en el ámbito del espacio de nombres en la misma unidad traducción y dinámicamente inicializado será inicializado en el orden en que aparece su definición en la unidad de traducción."

Ahora, como dije en el post Bajar y explicó in this defect report, estos miembros de datos estáticos no están definidos en una unidad de traducción, sino que se crean instancias en una unidad instanciación, como se explica en 2.1/1:

Cada unidad traducida de traducción se examina para generar una lista de instancias requeridas. [Nota: esto puede incluir instancias que se han solicitado explícitamente (14.7.2)]. Se encuentran las definiciones de las plantillas requeridas. la fuente de las unidades de traducción que contienen estas definiciones es r está destinado a estar disponible. [Nota: una implementación podría codificar suficiente información en la unidad de traducción traducida como para garantizar que la fuente no sea necesaria aquí. ] Todas las instancias requeridas se realizan para producir unidades de instanciación. [Nota: estos son similares a las unidades de traducción traducidas, pero no contienen referencias a plantillas desinstaladas ni definiciones de plantillas. ] El programa está mal formado si falla alguna instanciación.

El Punto de instanciación de un miembro de este tipo también en realidad no importa, porque un punto de creación de instancias de este tipo es el enlace de contexto entre una instancia y sus unidades de traducción - que define las declaraciones que son visibles (como se especifica en 14.6.4.1 , y cada uno de esos puntos de instanciación debe dar a las instancias el mismo significado, como se especifica en la regla de una definición en 3.2/5, última viñeta).

Si queremos la inicialización ordenada, tenemos que organizarla para no interferir con instancias, pero con declaraciones explícitas: esta es el área de especializaciones explícitas, ya que estas no son realmente diferentes a las declaraciones normales. De hecho, C++ 0x cambió su redacción de 3.6.2 a lo siguiente:

inicialización dinámica de un objeto no local con la duración de almacenamiento estático es o bien ordenada o desordenada. Las definiciones de miembros de datos estáticos de plantilla de clase explícitamente especializados han ordenado la inicialización. Otros miembros de datos estáticos de plantilla de clase (es decir, especializaciones instanciadas implícita o explícitamente) tienen una inicialización desordenada.


Esto significa a su código, que:

  • [1] y [2] comentó: No se hace referencia a los miembros de datos estáticos existir, por lo que sus definiciones (y también no sus declaraciones , ya que no hay necesidad de instanciación de B<int>) no se crean instancias. No se produce ningún efecto secundario.
  • [1] uncommented: B<int>::getB() se utiliza, que en sí mismo usa B<int>::mB, que requiere que exista miembro estático. La cadena se inicializa antes de main (en cualquier caso antes de esa declaración, como parte de la inicialización de objetos no locales). Nada utiliza B<int>::mInit, por lo que no se crea una instancia, por lo que no se crea ningún objeto de B<int>::InitHelper, lo que hace que su constructor no se utilice, lo que a su vez nunca asignará algo al B<int>::mB: Simplemente generará una cadena vacía.
  • [1] y [2] sin comentar: Que esto funcionó para usted es la suerte (o lo contrario :)). No hay ningún requisito para un orden particular de llamadas de inicialización, como se explicó anteriormente. Podría funcionar en VC++, fallar en GCC y trabajar en clang. No lo sabemos
  • [1] comentado, [2] sin comentar: El mismo problema - de nuevo, ambos miembros de datos estáticos son utiliza: B<int>::mInit es utilizado por B<int>::getHelper, y la creación de instancias de B<int>::mInit hará que su constructor para crear una instancia, que será use B<int>::mB - pero para su compilador, el orden es diferente en esta ejecución en particular (no se requiere un comportamiento no especificado para ser consistente entre diferentes ejecuciones): Inicializa primero B<int>::mInit, que operará en un objeto de cadena aún no construido.
2
  • [1] caso uncommented: que está bien. static InitHelper B<int>::mInit no existe. Si el miembro de la clase de plantilla (struct) no se utiliza, no se compila.

  • [1] y [2] caso sin comentar: Está bien. B<int>::getHelper() usa static InitHelper B<int>::mInit y mInit existe.

  • [1] comentado, [2] uncommented: me funciona en VS2008.

4

El problema es que las definiciones que da para las variables de miembros estáticos también son plantillas.

template<class T> 
std::string B<T>::mB; 
template<class T> 
typename B<T>::InitHelper B<T>::mInit; 

Durante la compilación, esto define realmente nada, ya que T no se conoce. Es algo así como una declaración de clase o una definición de plantilla, el compilador no genera código ni reserva almacenamiento cuando lo ve.

La definición se aplica implícitamente más adelante, cuando utiliza la clase de plantilla. Porque en el caso segfaulting no utiliza B < int> :: mInit, nunca se crea.

Una solución sería definir explicitamente el miembro sea necesario (sin inicializarla): Archivo Ponga algún lugar de origen una

template<> 
typename B<int>::InitHelper B<int>::mInit; 

Esto funciona básicamente de la misma manera como la definición de una clase de plantilla explicitamente.

+0

Creo que, en este mismo ejemplo, no tendría importancia poner la inicialización en otro archivo de encabezado, ya que todo está en la misma unidad de traducción. –

+0

Por supuesto. Es por eso que usé el modo condicional ("would"). Pero no soy un hablante nativo de inglés, por lo que es engañoso. Lo borré. – hirschhornsalz

+0

¿Entiende ahora por qué obtiene un miembro segfault/empty, o todavía no está claro? – hirschhornsalz

Cuestiones relacionadas