c++ - Contador Nifty/Schwarz, ¿cumple con la norma?
static-order-fiasco (2)
Creo que está garantizado para trabajar. De acuerdo con la norma ($ 3.6.2 / 1): "Los objetos con duración de almacenamiento estático (3.7.1) se inicializarán con cero (8.5) antes de que tenga lugar cualquier otra inicialización".
Como nifty_counter
tiene una duración de almacenamiento estático, se inicializa antes de que se cree el initializer
, independientemente de la distribución entre las unidades de traducción.
Edit: Después de releer la sección en cuestión y de considerar la entrada del comentario de @Tadeusz Kopec, no estoy seguro de si está bien definido tal como está ahora, pero es bastante trivial asegurar que esté bien definido: elimine el inicialización de la definición de nifty_counter
, por lo que parece que:
static int nifty_counter;
Dado que tiene una duración de almacenamiento estático, se inicializará con cero, incluso sin especificar un intializador, y eliminar el inicializador elimina cualquier duda sobre cualquier otra inicialización después de la inicialización de cero.
Tuve una discusión esta mañana con un colega sobre el orden de inicialización de la variable estática. Mencionó el contador Nifty / Schwarz y estoy (más o menos) confundido. Entiendo cómo funciona, pero no estoy seguro de que esto sea, técnicamente hablando, compatible con las normas.
Supongamos que los 3 archivos siguientes (los dos primeros están copiados en pasta de Más expresiones C ++ ):
//Stream.hpp
class StreamInitializer;
class Stream {
friend class StreamInitializer;
public:
Stream () {
// Constructor must be called before use.
}
};
static class StreamInitializer {
public:
StreamInitializer ();
~StreamInitializer ();
} initializer; //Note object here in the header.
//Stream.cpp
static int nifty_counter = 0;
// The counter is initialized at load-time i.e.,
// before any of the static objects are initialized.
StreamInitializer::StreamInitializer ()
{
if (0 == nifty_counter++)
{
// Initialize Stream object''s static members.
}
}
StreamInitializer::~StreamInitializer ()
{
if (0 == --nifty_counter)
{
// Clean-up.
}
}
// Program.cpp
#include "Stream.hpp" // initializer increments "nifty_counter" from 0 to 1.
// Rest of code...
int main ( int, char ** ) { ... }
... ¡Y aquí se encuentra el problema! Hay dos variables estáticas:
- "nifty_counter" en
Stream.cpp
; y - "inicializador" en
Program.cpp
.
Dado que las dos variables están en dos unidades de compilación diferentes, no hay una garantía oficial (AFAIK) de que nifty_counter
se inicialice a 0 antes de que se llame al constructor del initializer
.
Puedo pensar en dos soluciones rápidas como dos por qué esto "funciona":
- los compiladores modernos son lo suficientemente inteligentes como para resolver la dependencia entre las dos variables y colocar el código en el orden adecuado en el archivo ejecutable (muy poco probable);
-
nifty_counter
realidad se inicializa en "tiempo de carga" como dice el artículo y su valor ya está ubicado en el "segmento de datos" en el archivo ejecutable, por lo que siempre se inicializa "antes de que se ejecute cualquier código" (muy probable).
Ambos me parecen que dependen de una implementación no oficial pero posible. ¿Cumple este estándar o es "tan probable que funcione" que no deberíamos preocuparnos?
Creo que lo que falta en este ejemplo es cómo se evita la construcción de Stream, que a menudo no es portátil. Además del ingenioso contador, el papel de los inicializadores es construir algo como:
extern Stream in;
Cuando una unidad de compilación tiene la memoria asociada con ese objeto, ya sea que haya algún constructor especial antes de que se use el nuevo operador en el lugar, o en los casos que he visto, la memoria se asigna de otra manera para evitar conflictos. Me parece que hay un constructor no operativo en esta secuencia, entonces el orden de si el inicializador se llama primero o el constructor no operativo no está definido.
Asignar un área de bytes a menudo no es portátil, por ejemplo, para gnu iostream el espacio para cin se define como:
typedef char fake_istream[sizeof(istream)] __attribute__ ((aligned(__alignof__(istream))))
...
fake_istream cin;
llvm utiliza:
_ALIGNAS_TYPE (__stdinbuf<char> ) static char __cin [sizeof(__stdinbuf <char>)];
Ambos hacen cierta suposición sobre el espacio necesario para el objeto. Donde el contador de Schwarz se inicializa con una colocación nueva:
new (&cin) istream(&buf)
Prácticamente esto no parece tan portátil.
Me he dado cuenta de que algunos compiladores como gnu, microsoft y AIX tienen extensiones de compilador para influir en el orden de inicialización estática:
- Para Gnu esto es: Habilitar la
init-priority
con la__attribute__ ((init_priority (n)))
-f
y usar__attribute__ ((init_priority (n)))
. - En Windows con un compilador de Microsoft hay un #pragma ( http://support.microsoft.com/kb/104248 )