Почему статический член данных не может быть инициализирован? - PullRequest
6 голосов
/ 19 августа 2009

Я пытаюсь зарегистрировать группу классов на фабрике во время загрузки. Моя стратегия состоит в том, чтобы использовать статическую инициализацию, чтобы убедиться, что перед началом main () фабрика готова к работе. Эта стратегия, кажется, работает, когда я связываю свою библиотеку динамически, но не когда я связываю статически; когда я статически связываю, только некоторые из моих статических членов данных инициализируются.

Допустим, мой завод строит автомобили. У меня есть классы CarCreator, которые могут создавать несколько автомобилей, но не все. Я хочу, чтобы фабрика собирала все эти классы CarCreator, чтобы код, ищущий новый Автомобиль, мог отправляться на фабрику без необходимости знать, кто будет заниматься фактической конструкцией.

Итак, у меня есть

CarTypes.hpp

enum CarTypes
{
   prius = 0,
   miata,
   hooptie,
   n_car_types
};

MyFactory.hpp

class CarCreator
{
public:
   virtual Car * create_a_car( CarType ) = 0;
   virtual std::list< CarTypes > list_cars_I_create() = 0;
};

class MyFactory // makes cars
{
public:
   Car * create_car( CarType type );
   void factory_register( CarCreator * )

   static MyFactory * get_instance(); // singleton
private:
   MyFactory();

   std::vector< CarCreator * > car_creator_map;
};

MyFactory.cpp

MyFactory:: MyFactory() : car_creator_map( n_car_types );

MyFactory * MyFactory::get_instance() {
   static MyFactory * instance( 0 ); /// Safe singleton
   if ( instance == 0 ) {
      instance = new MyFactory;
   }
   return instance;
}

void MyFactory::factory_register( CarCreator * creator )
{
   std::list< CarTypes > types = creator->list_cars_I_create();
   for ( std::list< CarTypes >::const_iteator iter = types.begin();
         iter != types.end(); ++iter ) {
      car_creator_map[ *iter ] = creator;
   }
}

Car * MyFactory::create_car( CarType type ) 
{
   if ( car_creator_map[ type ] == 0 ) { // SERIOUS ERROR!
      exit();
   }
   return car_creator_map[ type ]->create_a_car( type );
}

...

Тогда у меня будут конкретные машины и конкретные создатели автомобилей:

Miata.cpp

class Miata : public Car {...};

class MiataCreator : public CarCreator {
public:
   virtual Car * create_a_car( CarType );
   virtual std::list< CarTypes > list_cars_I_create();
private:
   static bool register_with_factory();
   static bool registered;
};

bool MiataCreator::register_with_factory()
{
   MyFactory::get_instance()->factory_register( new MiataCreator );
   return true;
}

bool MiataCreator::registered( MiataCreator::register_with_factory() );

...

Повторюсь: при динамическом связывании моих библиотек MiataCreator :: register будет инициализирован, статически связывая мои библиотеки, он не будет инициализирован.

При статической сборке, когда кто-то отправляется на завод, чтобы запросить Miata, элемент miata car_creator_map будет указывать на NULL и программа завершит работу.

Есть ли что-то особенное в частных статических интегральных элементах данных, что их инициализация будет как-то пропущена? Статические члены-члены инициализируются только в том случае, если используется класс? Мои классы CarCreator не объявлены ни в одном заголовочном файле; они полностью живут в файле .cpp. Возможно ли, что компилятор встраивает функцию инициализации и как-то избегает вызова MyFactory :: factory_register?

Есть ли лучшее решение этой проблемы с регистрацией?

Невозможно перечислить все CarCreators в одной функции, явно зарегистрировать каждую из них на заводе-изготовителе, а затем гарантировать, что эта функция вызывается. В частности, я хочу связать несколько библиотек вместе и определить CarCreators в этих отдельных библиотеках, но по-прежнему использую единственную фабрику для их создания.

...

Вот некоторые ответы, которые я ожидаю, но которые не решают мою проблему:

1) ваша фабрика-одиночка не является поточно-ориентированной. а) Не имеет значения, я работаю только с одним потоком.

2) ваша Singleton Factory может быть неинициализирована, когда инициализируются ваши CarCreators (то есть вы потерпели статическую инициализацию) а) Я использую безопасную версию класса singleton, помещая экземпляр singleton в функцию. Если бы это было проблемой, я бы увидел вывод, если бы добавил оператор print к методу MiataCreator's::register_with_factory: нет.

Ответы [ 6 ]

8 голосов
/ 19 августа 2009

Я думаю, у вас фиаско статического порядка инициализации, но не с Фабрикой.

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

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

  1. Статические переменные, определенные в той же единице перевода (файл .cpp), будут инициализированы в указанном порядке
  2. Статические переменные, определенные в единице перевода, будут инициализированы до того, как любая функция или метод в этой единице перевода будут вызваны в первый раз.

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

В частности, нельзя полагаться на то, что MiataCreator :: register (определенный в Miata.cpp) инициализируется до того, как MyFactory :: create_car (определенный в MyFactory.cpp) будет вызван в первый раз.

Как и все неопределенное поведение, иногда вы получаете то, что хотите, а иногда нет, и самые странные, казалось бы, не связанные вещи (такие как статическое или динамическое связывание) могут измениться, если это работает так, как вы этого хотите. или нет.

Что вам нужно сделать, это создать метод статического метода доступа для зарегистрированного флага, определенного в Miata.cpp, и заставить фабрику MyFactory получить значение через этот метод доступа. Поскольку средство доступа находится в том же модуле преобразования, что и определение переменной, переменная будет инициализирована к моменту запуска средства доступа. Затем вам нужно вызвать этот метод доступа откуда-то.

3 голосов
/ 19 августа 2009

Если со статическим связыванием вы имеете в виду добавление всех объектных файлов (.o) в двоичный файл, это должно работать как динамический материал, если вы создали (.a) статическую библиотеку, компоновщик не будет связывать их внутри как только используемый объекты внутри статической библиотеки связаны между собой, и в этом случае ни один не используется явно.

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

Но если вам не удастся загрузить это когда-либо, это не сработает, сработает связывание объектных файлов и загрузка динамических библиотек, но статические библиотеки никогда не будут связываться без явных зависимостей.

1 голос
/ 30 января 2010

С помощью gcc вы можете добавить -Wl,--whole-archive myLib.a --Wl,--no-whole-archive. Это заставит компоновщик включать объекты, даже если на них не ссылаются. Это, однако, не переносимо.

1 голос
/ 19 августа 2009

Обычно со статическими библиотеками компоновщик извлекает только файлы .o из этой библиотеки, на которые ссылается основная программа. Так как вы не ссылаетесь на MiataCreator :: зарегистрирован или что-то в Miata.cpp, но полагаетесь на статическую инициализацию, компоновщик даже не включит этот код в ваш exe-файл, если он связан со статической библиотекой -

Проверьте полученный исполняемый файл с помощью nm или objdump (или dumpbin, если вы находитесь в Windows), действительно ли код для MiataCreator ::istered включен в exe-файл при статической ссылке.

Я не знаю, как заставить компоновщик включать все биты и кусочки статической библиотеки, хотя ...

0 голосов
/ 20 августа 2009

Лично я думаю, что вы нарушаете правила компоновщика.

Булевы переменные не используются 'bool MiataCreator :: register', если компоновщик не извлекает их из библиотеки в исполняемый файл (помните, что в исполняемом файле нет ссылки на функцию / глобал, который компоновщик не будет извлекать) объект им из библиотеки [Он ищет только объекты, которые в настоящий момент не определены в исполняемом файле])

Вы можете добавить некоторые операторы печати в 'bool MiataCreator :: register_with_factory ()', чтобы увидеть, вызывается ли он когда-либо. Или проверьте символы в вашем исполняемом файле, чтобы убедиться, что он там есть.

Некоторые вещи, которые я бы сделал:

// Return the factory by reference rather than pointer.
// If you return by pointer the user has to assume it could be NULL
// Also the way you were creating the factory the destructor was never
// being called (though not probably a big deal here) so there was no
// cleanup, which may be usefull in the future. And its just neater.
MyFactory& MyFactory::get_instance()
{
    static MyFactory   instance; /// Safe singleton 
    return instance;
}

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

bool MiataCreator::register_with_factory()
{
     MyFactory::get_instance()->factory_register( new MiataCreator );
     return true;
}
//
// I would hope that the linker does not optimize this out (but you should check).
// But the linker only pulls from (or searches in) static libraries 
// for references that are explicitly not defined.
bool MiataCreator::registered( MiataCreator::register_with_factory() );

Я бы сделал это:

MiataCreator::MiataCreator()
{
    // I would change factory_register to take a reference.
    // Though I would store it internall as a pointer in a vector.
    MyFactory::getInstance().factory_register(*this);
}

// In Cpp file.
static MiataCreator   factory;

Компоновщик знает об объектах и ​​конструкторах C ++ и должен извлекать все глобальные переменные, поскольку конструкторы потенциально могут иметь побочные эффекты (я знаю, что ваш bool также имеет, но я вижу, что некоторые компоновщики оптимизируют это).

В любом случае, это стоит моего 2с.

0 голосов
/ 19 августа 2009

Когда вы проверяете, находится ли элемент miata внутри карты? это до или после основного?
Единственная причина, по которой я мог придумать, - это доступ к элементам карты перед main () (например, при глобальной инициализации), что может иметь место до создания MiataCreator :: register (если он находится в другом модуле перевода)

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