3

I have a collection of worker classes, and I need to be able to construct instances of these classes dynamically with a single factory. The reasoning behind this is that new worker classes are written frequently and I'd rather not have to update a factory class per worker type every time I add a new worker class.

The way that this currently works is as follows: I've got a class called WorkerImplementationList with a static method:

template <typename T>
    WorkerImplementationList::registerWorkerFactoryMethod<T>(const std::string&)

that stores a pointer to T::newInstance in an internal data structure that will be retrieved by the factory later. In each of the worker classes have a static int called _dummyInstanceRegistrationVariable. In each of the worker classes' .cc files, I have the following line (using FooWorker as an example):

int FooWorker::_dummyInstanceRegistrationVariable =
    WorkerImplementationList::registerWorkerFactoryMethod<FooWorker>("FooWorker");

What I'd like to have happen is for the static variable to be initialized before any instances of the class are constructed. This seems to work just fine when the worker class is compiled into the same binary that contains main(). However, when FooWorker is in a library (say libblahapp_workers.a) and the main executable links that library, it looks like _dummyInstanceRegistrationVariable isn't initialized until after main() starts (I assume it's initialized when the first instance of FooWorker is constructed), which is too late for my purposes.

I've also tried constructing a WorkerImplementationRegisterer<T> object in global scope that registers the appropriate worker type when it's constructed, but here again I run into problems when the worker class is in a library external to main(); that globally-scoped object isn't constructed before main() begins.

Would statically linking my worker libraries solve this problem? Is there a more elegant solution that I'm missing? Any help you could give me would be greatly appreciated.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
alexras
  • 397
  • 1
  • 8
  • 3
    It sounds like you have fallen foul of the "static initialization order fiasco": http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.14. – Oliver Charlesworth Jun 03 '11 at 00:56
  • Are you sure you have diagnosed the problem correctly? Globally-scoped objects are supposed to be constructed before main() starts, period. – Nemo Jun 03 '11 at 00:58
  • @Nemo I've tried breaking in the constructor for the object in GDB and the breakpoint is never hit. I'm assuming it has to do with the object being globally scoped within a library, but I'm not entirely sure. – alexras Jun 03 '11 at 01:03
  • Hm. Similar problem: http://stackoverflow.com/questions/5646211/shared-library-constructor-is-not-executed What compiler is this? If g++, are you using g++ to link, or ld? (See also http://rachid.koucha.free.fr/tech_corner/shared_libs_tests.html) – Nemo Jun 03 '11 at 01:11
  • g++ v4.4.5. I'm using g++ to link. – alexras Jun 03 '11 at 01:16
  • You are using g++ to link both the .so and your executable? I would suggest reducing this to a trivial test case to verify the behavior... What you are saying is just plain not allowed by the language, so if you are correct (and have compiled and linked correctly), then this is a compiler/linker bug. – Nemo Jun 03 '11 at 01:19
  • Are you sure that you cannot make do with the initialization after `main()` starts - as long as it occurs before the first instance of the relevant type is created? Or, can you call an initialization function in `main()` that ensures the setup you need is done? – Jonathan Leffler Jun 03 '11 at 02:29
  • Initialization can occur after main() starts, but must happen before I need to start constructing workers. I'm trying to avoid adding any per-class boilerplate into main() if at all possible. – alexras Jun 03 '11 at 03:52

3 Answers3

3

This sounds like the linker pulling in only the objects it needs, and not pulling in the one with the global variable. Then the global simply doesn't exist, so initialization order is a moot point.

It's a real problem with static libraries, with no universal solution.

Armed with the knowledge that the linker grabs an entire compilation unit from the library at once (on all systems I've seen), you can probably figure out some way to put this global variable into a compilation unit that's needed.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
2

I find its usually best not to assume anything will run before main and and to try not to force that. The best way I find to implement these this is to initialise on first call. I would create a static method in FooWorker called create and construct all instances of FooWorker through that static method. The first thing the create method should do is check to see if FooWorker::_dummyInstanceRegistrationVariable has been initialised and if it hasn't, initialise it.

For example:

main.cc

...
#include <FooWorker.hh>
...

int main(){
   ...
   FooWorker *fw = FooWorker::create(...);
   ...
   delete fw;
   ...
}

FooWorker.hh

...
class FooWorker{
  ...
  static int _dummyInstanceRegistrationVariable;
  ...
  FooWorker *create(/*args*/){
     if(_dummyInstanceRegistrationVariable == 0)
        _dummyInstanceRegistrationVariable = WorkerImplementationList::registerWorkerFactoryMethod<FooWorker>("FooWorker");
     ...
  }
  ...
};
...

FooWorker.cc

...
#include "FooWorker.hh"
...
int FooWorker::_dummyInstanceRegistrationVariable = 0;
...

That's a very basic version of what I mean, but hopefully you get the drift.

  • Thanks for your advice. Unfortunately, that method won't really work for me; the main problem here is that FooWorker should be registered with WorkerImplementationList _before_ any instance of FooWorker is constructed. – alexras Jun 03 '11 at 03:09
2

iostreams in libstdc++ use file scope static variable for this. In iostream header you can find

static ios_base::Init __ioinit;

definition which takes care of iostreams initialization in constructor (including cin, cout, cerr). See the implementation of ios_base::Init, it may give you some ideas to work with.

Tomek
  • 4,554
  • 1
  • 19
  • 19