2010-12-07 3 views
5

Если у меня есть две статические переменные в разных единицах компиляции, то их порядок инициализации не определен. Этот урок хорошо изучен.C++ Инициализация статических переменных (еще раз)

Вопрос, который у меня есть: все статические переменные уже выделены, когда первый инициализируется. Другими словами:

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(); 
} 

Будет b_ptr пункт действительной части памяти, где выделяется и инициализируется B во время выполнения основной функции? На всех платформах?

Longer история:

Компиляция блок 1 является Qt библиотеки. Другой - мое приложение. У меня есть пары классов, основанных на QObject, что мне нужно создать экземпляр с помощью строки имени класса. Для этого я придумал шаблонный класс фабрики:

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; 

Затем я добавить этот макрос на каждом QObject определении подкласса:

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

Наконец я могу создать экземпляр класса по имени типа:

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; 
} 

Таким образом, класс получает статический экземпляр QMetaObject: Subclass::staticMetaObject из библиотеки Qt - он автогенерируется в Q_OBJECT макросов. Я думаю. А затем макрос FACTORY создает статический экземпляр ConcreteFactory<Subclass>. ConcreteFactory в своем конструкторе пытается ссылаться на подкласс :: staticMetaObject.

И я был очень доволен этой реализацией на linux (gcc), пока не скомпилировал его с Visual Studio 2008. По какой-то причине AbstractFactory :: m_Map был пуст во время выполнения, и отладчик не сломался на заводском конструкторе ,

Так вот откуда исходит запах статических варов, ссылающихся на другие статические вары.

Как я могу оптимизировать этот код, чтобы избежать всех этих ловушек?

+0

Ну, этот код сильно отличается от вашего оригинального вопроса ... в вашем исходном вопросе вы сохранили адрес глобальной переменной, но не получили доступ к нему до `main`, и к этому времени глобальный был полностью построен. Но теперь вы пытаетесь использовать его изнутри конструктора глобального объекта ConcreteFactory, который намного раньше, чем `main`. – 2010-12-07 18:21:30

+0

В подробном примере я сохраняю адрес статической переменной `staticMetaObject`. Но он доступен из `main` через функцию` create` во время выполнения - точно так, как я описал в упрощенном примере.Меня не интересует `static QMap <...> m_Map` - в моем реальном коде фактически завернута в статическую функцию, как это предлагал @Martin York. Таким образом, подробный пример касается ссылки на `staticMetaObject` из конструктора ConcreteFactory. – 2010-12-07 19:18:49

ответ

5

Да, стандарт позволяет это.

Есть целый ряд пунктов в разделе [basic.life], которые начинаются из

До жизни объекта уже начали, но после хранения, который объекта будет занимать был выделен или после срок службы объекта закончился и до хранилища , который занимал объект, является , повторно использован или выпущен, любой указатель, который относится к месту хранения, где obje ct будет или находится, может использоваться , но только ограниченным образом.

и есть сноска, которая указывает на то, что это конкретно относится к вашей ситуации

К примеру, до построения глобального объекта не-POD типа класса

1

Да. Все они расположены в разделе .data, который выделяется сразу (и это не куча).

Ввод его другим способом: если вы можете принять его адрес, тогда все в порядке, потому что оно, безусловно, не изменится.

+2

В стандарте нет понятия разделов .data. – 2010-12-07 17:03:11

2

Краткий ответ: его следует работать так, как вы его закодировали. См Бен Фойет Ответ

ответ:

ли что-то вроде этого:
Вместо того, чтобы позволить компилятору решить при создании Глобал, создавать их с помощью статических методов (со статической переменной функцией). Это означает, что они будут детерминистически созданы при первом использовании (и уничтожены в обратном порядке создания).

Даже если один глобальный использует другой при его строительстве, используя этот метод, гарантии, что они будут созданы в требуемом порядке и, таким образом, будут доступны для использования другими (следить за циклами).

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(); 
} 

Хотя слово предупреждения.

  • Глобал является индикатором плохого дизайна.
  • Глобалы, которые зависят от других глобалов, являются еще одним запахом кода (особенно во время строительства/разрушения).
+0

Вы используете компилятор? – 2010-12-07 17:03:29

+0

@ak: Да, он делает. – jwueller 2010-12-07 17:04:54

+0

ОП спросил об их назначении, а не о инициализации. Сравните `static int i` с` static int * i`. – ruslik 2010-12-07 17:09:44

1

Если B имеет конструктор, такой как A, то порядок, который они вызывают, не определен. Таким образом, ваш код не будет работать, если вам не повезет. Но если B не требует какого-либо кода для его инициализации, тогда ваш код будет работать. Это не реализация.

1

Ах, но идея о том, что статические переменные «не инициализированы», неверна. Они всегда инициализируются, просто не обязательно с вашим инициализатором. В частности, все статические переменные создаются со значением нуля перед любой другой инициализацией. Для объектов класса члены равны нулю. Следовательно, global_a.b_ptr выше всегда будет действительным указателем, первоначально NULL и позже & global_b. Эффектом этого является то, что использование не-указателей не определен, не определен, в частности, этот код хорошо определен (в C):

// unit 1 
int a = b + 1; 

// unit 2 
int b = a + 1; 

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

гарантии нулевой инициализации используется с этой схемой:

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

, который обеспечивает либо невозврат из-за бесконечной рекурсии, либо правильное инициализированное значение.

Смежные вопросы