views:

649

answers:

6

I'm trying to register a bunch of classes with a factory at load time. My strategy is to harness static initialization to make sure that before main() begins, the factory is ready to go. This strategy seems to work when I link my library dynamically, but not when I link statically; when I link statically, only some of my static data members get initialized.

Let's say my factory builds Cars. I have CarCreator classes that can instantiate a handful of cars, but not all. I want the factory to collect all of these CarCreator classes so that code looking for a new Car can go to the factory without having to know who will be doing the actual construction.

So I've got

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

...

Then I'll have specific cars and specific car creators:

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

...

To reiterate: dynamically linking my libraries, MiataCreator::registered will get initialized, statically linking my libraries, it will not get initialized.

With a static build, when someone goes to the factory to request a Miata, the miata element of the car_creator_map will point to NULL and the program will exit.

Is there anything special with private static integral data members that their initialization will be somehow skipped? Are static data members only initialized if the class is used? My CarCreator classes are not declared in any header file; they live entirely within the .cpp file. Is it possible that the compiler is inlining the initialization function and somehow avoiding the call to MyFactory::factory_register?

Is there a better solution to this registration problem?

It is not an option to list iall of the CarCreators in a single function, register each one explicitly with the factory, and then to guarantee that the function is called. In particular, I want to link several libraries together and define CarCreators in these separate libraries, but still use a singular factory to construct them.

...

Here are some responses I am anticipating but which do not address my problem:

1) your singleton Factory isn't thread safe. a) Shouldn't matter, I'm working with only a single thread.

2) your singleton Factory may be uninitialized when your CarCreators are being initialized (i.e. you've got a static initialization fiasco) a) I'm using a safe version of the singleton class by putting the singleton instance into a function. If this were a problem, I should see output if I added a print statement to the MiataCreator's::register_with_factory method: I don't.

+2  A: 

If with static linking you mean adding all object files(.o) to the binary, that should work like the dynamic stuff, if you made a (.a) static library the linker will not link them inside as only the used objects inside the static library are linked and in this case none is used explicitly.

All the auto-registering techinques depend on load-time code and its ways to avoid the static fiasco, like the function that creates the object and returns it on demand.

But if you don't manage to load that ever it won't work, linking object files together works, and loading dynamic libraries as well, but static libraries will never link without explicit dependencies.

Arkaitz Jimenez
A: 

When do you check if the miata element is inside the map? is it before or after main?
The only reason I could think of is accessing the map elements before main() (such as in global initialization), which may take place before the creation of MiataCreator::registered (if it's in a different translation unit)

Oren S
I only read from the map after main() has begun.
Andrew
+1  A: 

Generally with static libaries, the linker will only pull out the .o files from that library which the main program references. Since you're not referencing MiataCreator::registered or really anything in Miata.cpp but rely on static initiailization the linker won't even include that code in your exe if it's linked from a static library-

Check the resulting executable with nm or objdump(or dumpbin if you are on windows) whether the code for MiataCreator::registered is actually included in the exe file when you link statically.

I don't know how to force the linker to include every bits and pieces of a static library though..

nos
Yep thats what I was thinking.
Martin York
This is only half of the explanation. The linker is acting like a garbage collector: it explores all pointers (function calls) starting from something that's active (main). If an object is never reached (a compilation unit is never reached) during the exploration of active memory (functions potentially reached from main()), then the garbage collector deletes it (the linker doesn't include it). (I'll continue this in my next comment)
Andrew
The only way to signal to the linker that Miata.cpp should be included is to have some central function (e.g one that lives in MyFactroy.cpp) include a reference to some piece of data in Miata.cpp My argument is that this extra step is as restrictive as the original problem: MyFactory (or someone central) has to know about all the CarCreators. There is no solution in which MyFactory can wait until load time for unknown CarCreators to come to it with registration requests. I cannot put CarCreators in other libraries.
Andrew
+1  A: 

I think you have a static initialization order fiasco, but not with the Factory.

It's not that it's the registered flag is not getting initialized, it's just not getting initialized soon enough.

You cannot rely on static initialization order except to the extent that:

  1. Static variables defined in the same translation unit (.cpp file) will be initialized in the order listed
  2. Static variables defined in a translation unit will be initialized before any function or method in that translation unit is invoked for the first time.

What you cannot rely on is that a static variable will be initialized before a function or method in some other translation unit is invoked for the first time.

In particular, you cannot rely on MiataCreator::registered (defined in Miata.cpp) to be initialized before MyFactory::create_car (defined in MyFactory.cpp) is invoked for the first time.

Like all undefined behavior, sometimes you will get what you want, and sometimes you won't, and the strangest most seemingly-unrelated things (such as static versus dynamic linking) can change whether it works the way you want it to or not.

What you need to do is create static accessor method for the registered flag that is defined in Miata.cpp, and have the MyFactory factory get the value through this accessor. Since the accessor is in the same translation unit as the variable definition, the variable will be initialized by the time the accessor runs. You then need to call this accessor from somewhere.

Tyler McHenry
So static data members are not always initialized at load time. Is there anything that is?I would like to avoid having MyFactory know which classes it has to talk to. Before I started implementing the CarCreators, I just had a huge switch statement in MyFactory: switch ( car_type ) { case miata: return new Miata;One of the big problems with this setup is that all the Car classes had to be defined in the same library with the factory. As that library got large, development time slowed.
Andrew
"2. Static variables defined in a translation unit will be initialized before any function or method in that translation unit is invoked for the first time." There's a chicken and egg problem: the knowledge that the translation unit needs to be invoked lives in the translation unit. There's no way, then, to communicate "hey, use this code" without hard coding that instruction somewhere centrally (e.g. in Factory.cpp)? That's terribly depressing.
Andrew
Thats theory, but even boost guys do rely on static initialization before main http://www.boost.org/doc/libs/1_39_0/libs/flyweight/doc/tutorial/technical.html
Arkaitz Jimenez
There it is in the standard:http://www-d0.fnal.gov/KAI/doc/ANSI-Dec-1996/basic.html#basic.start.init"It is implementation-defined whether a is defined before main is entered or whether its definition is delayed until a is first used in main."My strategy will not work. Thanks for your help.
Andrew
oops -- that's not the sentence I meant to quote: "It is implementation-defined whether the dynamic initialization ... of an object of namespace scope with static storage duration is done before the first statement of main or deferred to any point in time after the first statement of main but before the first use of a function or object defined in the same translation unit." Ergo, if a translation unit is never activated, the static data in that translation unit is not guaranteed to ever be initialized.
Andrew
A: 

Personally I think you are fallowing foul of the linker.

The boolean variables are not being used 'bool MiataCreator::registered' os the linker is not pulling them from the lib into the executable (remember if there is no reference to a function/global in the executable the linker will not pull them object from the lib [It only looks for objects that are currently undefined in the executable])

You could add some print statements in 'bool MiataCreator::register_with_factory()' to see if it is ever being called. Or check the symbols in your executable to verify that it is there.

Some things I would do:

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

Rather than have a two step initialisation of the object. Which I suspec is failing because of the linker. Create an instance of your factory and get the constructor to register it.

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

I would do this:

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;

The linker knows about C++ objects and constructors and should pull all global variables as the constructors can potentially have side affects (I know your bool does as well, but I can see some linkers optimizing that out).

Anyway thats my 2c worth.

Martin York
A: 

With gcc, you can add -Wl,--whole-archive myLib.a --Wl,--no-whole-archive. This will force the linker to include the objects even if not refered to. This is, however, not portable.

Jonathan Brannan