C ++ Инициализация статических переменных (еще раз) - PullRequest
5 голосов
/ 07 декабря 2010

Если у меня две статические переменные в разных единицах компиляции, то порядок их инициализации не определен. Этот урок хорошо усвоен.

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

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 выделяется и инициализируется во время выполнения основной функции? На всех платформах?

Более длинная история:

Единицей компиляции 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 в своем конструкторе пытается ссылаться на Subclass :: staticMetaObject.

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

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

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

Ответы [ 5 ]

5 голосов
/ 07 декабря 2010

Да, стандарт допускает это.

В разделе [basic.life] есть несколько параграфов, которые начинаются

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

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

Например, допостроение глобального объекта не класса POD типа

2 голосов
/ 07 декабря 2010

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

Длинный ответ:

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

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

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

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

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

Ах, но идея о том, что статические переменные "не инициализированы", совершенно ошибочна. Они всегда инициализируются, но не обязательно с вашим инициализатором. В частности, все статические переменные создаются со значением ноль перед любой другой инициализацией. Для объектов класса члены равны нулю. Следовательно, 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; }
}

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

1 голос
/ 07 декабря 2010

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

1 голос
/ 07 декабря 2010

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

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

...