Inicialización de miembro estático de C ++ (diversión de plantilla en el interior)

Para la inicialización de miembros estáticos, utilizo una estructura helper anidada, que funciona bien para las clases sin plantillas. Sin embargo, si la clase adjunta es parametrizada por 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).

#include  #include  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 struct B { struct InitHelper { InitHelper() { B::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 std::string B::mB; //[4] template typename B::InitHelper B::mInit; int main(int argc, char* argv[]) { std::cout << "A = " << A::getA() << std::endl; // std::cout << "B = " << B::getB() << std::endl; // [1] // B::getHelper(); // [2] } 

Con g ++ 4.4.1:

  • [1] y [2] comentaron:

      A = Hola, soy A. 

    Funciona según lo previsto

  • [1] sin comentar:

      A = Hola, soy A.
     B = 

    Yo esperaría, que InitHelper inicialice mB

  • [1] y [2] sin comentar:
      A = Hola, soy A.
     B = Hola, soy B. 

    Funciona según lo previsto

  • [1] comentó, [2] sin comentar:
    Segfault en la etapa de inicialización estática en [3]

Por lo tanto, mi pregunta: ¿Es esto un error del comstackdor o el error se encuentra entre el monitor y la silla? Y si este último es el caso: ¿existe una solución elegante (es decir, sin llamar explícitamente a un método de inicialización estático)?

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

A menos que un miembro de una plantilla de clase o una plantilla de miembro haya sido explícitamente instanciado o explícitamente especializado, la especialización del miembro se instanciará implícitamente cuando se haga referencia a la especialización en un contexto que requiera 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.

Esto fue discutido en usenet hace algún tiempo, mientras intentaba responder a otra pregunta en stackoverflow: Punto de instanciación de los miembros de datos estáticos . Creo que vale la pena reducir el caso de prueba, y teniendo en cuenta cada escenario de forma aislada, así que vamos a verlo más general primero:


 struct C { C(int n) { printf("%d\n", n); } }; template struct A { static C c; }; template C A::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 aún no crea ningún miembro de datos, debido a 14.7.1 :

“… 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”.

La definición de algo (= entidad) es necesaria cuando esa entidad es “utilizada”, de acuerdo con la regla de una definición que define esa palabra (en 3.2/2 ). En particular, si todas las referencias provienen de plantillas desinstaladas, miembros de una plantilla o un sizeof expresiones o elementos similares que no “usan” la entidad (ya que no la evalúan potencialmente, o simplemente no existen aún 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 ejemplifica las declaraciones de los miembros de datos estáticos, es decir, instanciará cualquier plantilla necesaria para procesar esa statement. 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 hará que los dos miembros de datos estáticos existan, pero la pregunta es: ¿cómo es el orden de inicialización? En una lectura simple, uno podría pensar que se aplica 3.6.2/1 , que dice (énfasis mío):

“Los objetos con duración de almacenamiento estático definidos en el ámbito del espacio de nombres en la misma unidad de traducción e inicializados dinámicamente deberán inicializarse en el orden en que aparece su definición en la unidad de traducción”.

Ahora, como se dice en la publicación de Usenet y se explica en este informe de defectos , estos miembros de datos estáticos no están definidos en una unidad de traducción, pero se crean instancias en una unidad de instanciación , como se explica en 2.1/1 :

Cada unidad de traducción traducida se examina para generar una lista de instancias requeridas. [Nota: esto puede incluir instancias que se han solicitado explícitamente (14.7.2). ] Las definiciones de las plantillas requeridas se encuentran. Está definido por la implementación si se requiere que esté disponible la fuente de las unidades de traducción que contienen estas definiciones. [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 progtwig está mal formado si falla alguna instanciación.

El punto de instanciación de dicho miembro tampoco importa realmente, ya que dicho punto de instanciación es el vínculo de contexto entre una instanciación y sus unidades de traducción: define las declaraciones que son visibles (como se especifica en 14.6.4.1 , y cada una de ellas). esos puntos de instanciación deben 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 las 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:

La inicialización dinámica de un objeto no local con duración de almacenamiento estática está 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 para su código, que:

  • [1] y [2] comentaron: No existe ninguna referencia a los miembros de datos estáticos, por lo que sus definiciones (y tampoco sus declaraciones, ya que no hay necesidad de creación de instancia de B ) no se instancian. No se produce ningún efecto secundario.
  • [1] B::getB() se usa B::getB() , que en sí mismo usa B::mB , que requiere que exista ese miembro estático. La cadena se inicializa antes de main (en cualquier caso antes de esa statement, como parte de la inicialización de objetos no locales). Nada usa B::mInit , por lo que no se B::mInit una instancia, por lo que no se crea ningún objeto de B::InitHelper , lo que hace que su constructor no se use, lo que a su vez nunca le asignará algo a B::mB : Simplemente generará una cadena vacía.
  • [1] y [2] comentó: Que esto funcionó para usted es 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] comentó, [2] comentar: el mismo problema: una vez más, se utilizan ambos miembros de datos estáticos: B::mInit es utilizado por B::getHelper , y la B::getHelper instancias de B::mInit causará la creación de una instancia de su constructor, que usará B::mB – pero para su comstackdor, el orden es diferente en esta ejecución en particular (no se requiere un comportamiento no especificado para ser consistente entre diferentes ejecuciones): Inicializa B::mInit primero, que operará en un objeto de cadena aún no construido.

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

 template std::string B::mB; template typename B::InitHelper B::mInit; 

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

La definición ocurre implícitamente más tarde, cuando usa la clase de plantilla. Porque en el caso segfaulting no utiliza B :: mInit, nunca se crea.

Una solución sería definir explícitamente el miembro necesario (sin inicializarlo): poner un archivo fuente en alguna parte

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

Esto funciona básicamente de la misma manera que la definición explícita de una clase de plantilla.

  • [1] caso sin comentario: está bien. static InitHelper B::mInit no existe. Si el miembro de la clase de plantilla (struct) no se utiliza, no se comstack.

  • [1] y [2] caso sin comentario: está bien. B::getHelper() usa static InitHelper B::mInit y mInit existe.

  • [1] comentó, [2] sin comentarios: me funciona en VS2008.