2010-12-07 7 views
5

Si tengo dos variables estáticas en diferentes unidades de compilación, entonces su orden de inicialización no está definida. Esta lección está bien aprendidaC++ Inicialización de variables estáticas (Una vez más)

La pregunta que tengo: son todas las variables estáticas ya asignadas, cuando la primera se está inicializando. En otras palabras:

static A global_a; // in compilation unit 1 
static B global_b; // in compilation unit 2 

struct A { 
    A() { b_ptr = &global_b; } 
    B *b_ptr; 

    void f() { b_ptr->do_something(); } 
} 

int main() { 
    global_a.f(); 
} 

Se b_ptr punto a una pieza válida de la memoria, donde se asigna B y se inicializa en el momento de la ejecución de la función principal? En todas las plataformas? historia

más larga:

La unidad de compilación 1 es la biblioteca Qt. El otro es mi aplicación. Tengo un par de clases derivadas de QObject, que necesito poder instanciar por la cadena de nombre de clase. Para esto he venido para arriba con una clase de fábrica con plantilla:

class AbstractFactory { 
public: 
    virtual QObject *create() = 0; 
    static QMap<const QMetaObject *, AbstractFactory *> m_Map; 
} 
QMap<const QMetaObject *, AbstractFactory *> AbstractFactory::m_Map; //in .cpp 

template <class T> 
class ConcreteFactory: public AbstractFactory { 
public: 
    ConcreteFactory() { AbstractFactory::m_Map[&T::staticMetaObject] = this; } 
    QObject *create() { return new T(); } 
} 

#define FACTORY(TYPE) static ConcreteFactory <TYPE> m_Factory; 

Luego añadir esta macro en cada definición subclase QObject:

class Subclass : public QObject { 
    Q_OBJECT; 
    FACTORY(Subclass); 
} 

fin puedo crear una instancia de una clase por el nombre del tipo:

QObject *create(const QString &type) { 
    foreach (const QMetaObect *meta, AbstractFactory::m_Map.keys() { 
     if (meta->className() == type) { 
      return AbstractFactory::m_Map[meta]->create(); 
     } 
    } 
    return 0; 
} 

Así que la clase obtiene una instancia estática QMetaObject: Subclass::staticMetaObject de la biblioteca Qt - se genera automáticamente en Q_OBJECT macro, creo. Y luego, la macro FACTORY crea una instancia estática ConcreteFactory<Subclass>. ConcreteFactory en su constructor intenta hacer referencia a Subclass :: staticMetaObject.

Y estuve muy contento con esta implementación en Linux (gcc), hasta que la compilé con Visual Studio 2008. Por alguna razón AbstractFactory :: m_Map estaba vacía en el tiempo de ejecución, y el depurador no se rompería en el constructor de la fábrica .

Así que aquí es donde viene el olor de los vars estáticos que hacen referencia a otros vars estáticos.

¿Cómo puedo optimizar este código para evitar todas estas trampas?

+0

Bueno, este código es bastante diferente de su pregunta original ... en su pregunta original, almacenó la dirección de una variable global, pero no accedió hasta 'main', momento en el cual el global fue construido por completo. Pero ahora está intentando usarlo desde dentro del constructor de un objeto global 'ConcreteFactory', que es mucho más antiguo que' main'. –

+0

En el ejemplo detallado, guardo la dirección de la variable estática 'staticMetaObject'. Pero se accede desde 'main' a través de la función' create' en el tiempo de ejecución, exactamente como lo describí en el ejemplo simplificado.No me preocupa el 'QMap estático <...> m_Map' - en mi código real está envuelto en una función estática tal como lo sugirió @Martin York. Entonces, el ejemplo detallado es sobre hacer referencia a 'staticMetaObject' del constructor' ConcreteFactory'. –

Respuesta

5

Sí, el estándar lo permite.

Hay una serie de apartados en la sección [basic.life] que comienzan a cabo

Antes de que el tiempo de vida de un objeto ha comenzado pero después del almacenamiento, que el objeto ocupará ha sido asignaron o, después de la vida útil de un objeto ha finalizado y antes del almacenamiento que el objeto ocupado es reutilizado o liberado, cualquier puntero que haga referencia a la ubicación de almacenamiento donde ct será o fue localizado puede usarse pero solo de manera limitada.

y hay una nota que indica que esto se aplica específicamente a su situación

Por ejemplo, antes de la construcción de un objeto global de la no-POD tipo de clase

+0

Es bueno tener números de sección. Pero bien encontrado. –

+0

Estoy bastante seguro de que los nombres de las secciones son bastante consistentes en todas las versiones del estándar, pero no tengo el mismo nivel de comodidad con respecto a la numeración. Es la sección 3.8 de C++ 0x FCD, por ejemplo. –

+0

Ooops. Perdió ese nombre obvio. :-) Todavía es temprano para mí. –

1

Sí. Todos se encuentran en la sección .data, que se asigna a la vez (y no es un montón).

Poniéndolo de otra manera: si puede tomar su dirección, entonces está bien, porque seguramente no cambiará.

+2

La norma no tiene concepto de secciones .data. –

2

Respuesta corta: Debe funcionar como lo ha codificado. Ver Ben Voigt respuesta

Respuesta larga:

hacer algo como esto:
En lugar de dejar que el compilador de decidir cuando se crean las variables globales, crearlos a través de métodos estáticos (con las variables de función estáticas). Esto significa que se crearán determinísticamente en el primer uso (y se destruirán en el orden inverso de la creación).

Incluso si un servidor global utiliza otro durante su construcción con este método, garantiza que se crearán en el orden requerido y estarán disponibles para su uso por el otro (cuidado con los bucles).

struct A 
{ 
    // Rather than an explicit global use 
    // a static method thus creation of the value is on first use 
    // and not at all if you don't want it. 
    static A& getGlobalA() 
    { 
     static A instance; // created on first use (destroyed on application exit) 
    // ^^^^^^ Note the use of static here. 
     return instance; // return a reference. 
    } 
    private: 
    A() 
     :b_ref(B::getGlobalB())  // Do the same for B 
    {}        // If B has not been created it will be 
            // created by this call, thus guaranteeing 
            // it is available for use by this object 
    } 
    B& b_ref; 
    public: 
    void f() { b_ref.do_something(); } 
}; 

int main() { 
    a::getGlobalA().f(); 
} 

Aunque una palabra de advertencia.

  • Globales son una indicación de mal diseño.
  • Globals que dependen de otros globals es otro olor a código (especialmente durante la construcción/destrucción).
+0

implementación del compilador, quieres decir? –

+0

@ak: Sí, lo hace. – jwueller

+0

OP preguntó acerca de su asignación y no la inicialización. Compare 'static int i' con' static int * i'. – ruslik

1

Si B tiene un constructor, como A tiene, el orden en que se llaman no está definido. Entonces tu código no funcionará a menos que tengas suerte. Pero si B no requiere ningún código para inicializarlo, entonces su código funcionará. No está definido por la implementación.

+2

Esa no es la pregunta que se hace. La pregunta que se hace es sobre el clima, la dirección de la variable global_b está disponible para el constructor de global_a (incluso si global_b aún no ha sido inicializado por su constructor). –

+0

Sí, tienes toda la razón. Lo siento. Ignora mi respuesta, ve y lee Ben Voigt's. – TonyK

1

Ah, pero la idea de que las variables estáticas "no se inicializan" es bastante errónea. Siempre se inicializan, pero no necesariamente con tu inicializador. En particular, todas las variables estáticas se crean con valor cero antes de cualquier otra inicialización. Para objetos de clase, los miembros son cero. Por lo tanto, global_a.b_ptr anterior siempre será un puntero válido, inicialmente NULL y más tarde & global_b. El efecto de esto es que el uso de los no punteros no está especificado, no definido, en particular, este código está bien definida (en C):

// unit 1 
int a = b + 1; 

// unit 2 
int b = a + 1; 

main ... printf("%d\n", a + b); // 3 for sure 

La garantía cero de inicialización se utiliza con este patrón:

int get_x() { 
    static int init; 
    static int x; 
    if(init) return x; 
    else { x = some_calc(); init = 1; return x; } 
} 

que asegura una devolución debido a la recursión infinita o al valor inicializado correctamente.

+0

A menudo me encuentro con un problema donde la inicialización automática de las variables miembro funciona de manera diferente en Debud y Release. Por lo tanto, si olvido asignar 0 (o NULL) a un puntero de miembro, es posible que no se inicialice en la depuración (es decir, que contenga valor de basura). ¿Funciona de manera diferente para las variables estáticas? –

Cuestiones relacionadas