Подтвердить что ты не робот

Глобальный порядок инициализации С++ игнорирует зависимости?

Я думаю, что моя проблема лучше всего описана в коде:

#include <stdio.h>

struct Foo;

extern Foo globalFoo;

struct Foo {
    Foo() {
        printf("Foo::Foo()\n");
    }

    void add() {
        printf("Foo::add()\n");
    }

    static int addToGlobal() {
        printf("Foo::addToGlobal() START\n");

        globalFoo.add();

        printf("Foo::addToGlobal() END\n");

        return 0;
    }
};

Foo globalFoo;

int dummy = Foo::addToGlobal();

int main() {
    printf("main()\n");

    return 0;
}

Вышеприведенные отпечатки (с gcc 4.4.3):

Foo::Foo()
Foo::addToGlobal() START
Foo::add()
Foo::addToGlobal() END
main()

Это то, что я ожидаю, и кажется логичным.

Однако, когда я заменяю следующие строки:

Foo globalFoo;
int dummy = Foo::addToGlobal();

в это:

int dummy = Foo::addToGlobal();
Foo globalFoo;

программа выводит следующее:

Foo::addToGlobal() START
Foo::add()
Foo::addToGlobal() END
Foo::Foo()
main()

Кажется, что экземпляры методов Foo вызываются с использованием экземпляра, который еще не создан! Что-то простое, как перемещение объявления переменной в глобальной области, влияет на поведение программы, и это заставляет меня поверить (1) порядок инициализации глобалов не определен и (2) порядок инициализации глобальных переменных игнорирует все зависимости. Это верно? Можно ли убедиться, что конструктор Foo вызывается до инициализации dummy?

Проблема, которую я пытаюсь решить, статически ставит репозиторий элементов (статический экземпляр Foo). В моей текущей попытке я использую макрос, который (помимо всего прочего) создает глобальную переменную (в анонимном пространстве имен, чтобы избежать столкновения имен), инициализация которой инициирует статическую инициализацию. Возможно, я решу проблему с неправильным углом? Есть ли лучшая альтернатива? Спасибо.

4b9b3361

Ответ 1

В порядке инициализации прочитайте ответ здесь.

О том, как решить проблему инициализации, вы можете заставить global быть статической локальной переменной в функции. Там стандартные гарантии, что статическая локальная переменная будет инициализирована при первом вызове функции:

class Foo {
public:
   static Foo& singleton() {
      static Foo instance;
      return instance;
   }
};

Затем ваши другие глобальные переменные будут обращаться к переменной как:

Foo::singleton().add();

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

Ответ 2

(1) порядок инициализации глобалов не определен

Глобальные переменные в одной единице перевода (исходный файл) инициализируются в том порядке, в котором они определены.

Порядок инициализации глобальных переменных в разных единицах перевода неуточнен.

(2) порядок инициализации глобалей игнорирует все зависимости

Right.

Можно ли убедиться, что конструктор Foo вызывается до инициализации фиктивного файла?

Да, если globalFoo определено до dummy и они находятся в одной и той же единицы перевода.

Один вариант - иметь статический указатель на глобальный экземпляр; такой указатель будет инициализирован нулем до того, как произойдет любая динамическая инициализация; addToGlobal может затем проверить, является ли указатель нулевым; Если это так, то это первый раз, когда используется глобальное значение, а addToGlobal может создать глобальный Foo.

Ответ 3

Вы правы, инициализация глобальных блоков между единицами перевода undefined. Это можно обойти, используя singleton pattern. Однако следует предупредить, что этот шаблон дизайна часто используется неправильно. Также следует предупредить, что порядок или уничтожение глобальных символов также undefined, если у вас есть зависимости в деструкторах.

Ответ 4

В С++ отсутствует что-то вроде Ada pragma elaborate, поэтому вы не можете делать никаких предположений о начале инициализации заказа., Это отстой, но что дизайн.

Ответ 5

Как насчет того, чтобы статическая глобальная переменная была указателем, инициализированным значением nullptr. Затем, прежде чем другой глобальный объект попытается использовать объект, проверьте его создание и при необходимости создайте. Это работало для меня для создания глобального реестра создателей классов, где можно было добавлять новые классы без изменения файла, который обрабатывал реестр. то есть.

class Factory {
   static map<string, Creator*>* theTable;
   static void register1(const string& string, Creator* creator);
...
};
...
map<string, Creator*>* Factory::theTable= nullptr;
void Factory::register1(const string& theName, Creator* creator) {   
    if (!theTable) theTable=new map<string, Creator*>;
    (*theTable)[theName]=creator;
}

Это скомпилировано и работает с VС++ в Visual Studio 2015.

Я пытался использовать до этого

class Factory {
    public:
      static map<string, Creator*>  theTable;
      static map<string, Creator*>& getTable();
      static void register1(const string& string, Creator* creator);
}
map<string, Creator*>  Factory::theTable;
map<string, Creator*>& Factory::getTable() {
   return theTable;
}
void Factory::register1(const string& theString, Creator* creator) {
   getTable()[theString]=creator; // fails if executed before theTable is created

}

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