Here is a sustainable idiom for managing factories that resolve at runtime. I've used this in the past to support fairly sophisticated behavior. I favor simplicity and maintainability without giving up much in the way of functionality.
TLDR:
- Avoid static initialization in general
- Avoid "auto-loading" techniques like the plague
- Communicate ownership of objects AND factories
- Separate usage and factory management concerns
Using Runtime Factories
Here is the base interface that users of this factory system will interact with. They shouldn't need to worry about the details of the factory.
class BaseObject {
public:
virtual ~BaseObject() {}
};
BaseObject* CreateObjectFromStream(std::istream& is);
As an aside, I would recommend using references, boost::optional
, or shared_ptr
instead of raw pointers. In a perfect world, the interface should tell me who owns this object. As a user, am I responsible for deleting this pointer when it's given to me? It's painfully clear when it's a shared_ptr
.
Implementing Runtime Factories
In another header, put the details of managing the scope of when the factories are active.
class RuntimeFactory {
public:
virtual BaseObject* create(std::istream& is) = 0;
};
void RegisterRuntimeFactory(RuntimeFactory* factory);
void UnregisterRuntimeFactory(RuntimeFactory* factory);
I think the salient point in all of this is that usage is a different concern from how the factories are initialized and used.
We should note that the callers of these free functions own the factories. The registry does not own them.
This isn't strictly necessary, though it offers more control when and where these factories get destroyed. The point where it matters is when you see things like "post-create" or "pre-destroy" calls. Factory methods with these sorts of names are design smells for ownership inversion.
Writing another wrapper around this to manage the factories life-time would be simple enough anyway. It also lends to composition, which is better.
Registering Your New Factory
Write wrappers for each factory registration. I usually put each factory registration in its own header. These headers are usually just two function calls.
void RegisterFooFactory();
void UnregisterFooFactory();
This may seem like overkill, but this sort of diligence keeps your compile times down.
My main
then is reduced to a bunch of register and unregister calls.
#include <foo_register.h>
#include <bar_register.h>
int main(int argc, char* argv[]) {
SetupLogging();
SetupRuntimeFactory();
RegisterFooFactory();
RegisterBarFactory();
// do work...
UnregisterFooFactory();
UnregisterBarFactory();
CleanupLogging();
return 0;
}
Avoid Static Init Pitfalls
This specifically avoids objects created during static loading like some of the other solutions. This is not an accident.
- The C++ spec won't give you useful assurances about when static loading will occur
- You'll get a stack trace when something goes wrong
- The code is simple, direct, easy to follow
Implementing the Registry
Implementation details are fairly mundane, as you'd imagine.
class RuntimeFactoryRegistry {
public:
void registerFactory(RuntimeFactory* factory) {
factories.insert(factory);
}
void unregisterFactory(RuntimeFactory* factory) {
factories.erase(factory);
}
BaseObject* create(std::istream& is) {
std::set<RuntimeFactory*>::iterator cur = factories.begin();
std::set<RuntimeFactory*>::iterator end = factories.end();
for (; cur != end; cur++) {
// reset input?
if (BaseObject* obj = (*cur)->create(is)) {
return obj;
}
}
return 0;
}
private:
std::set<RuntimeFactory*> factories;
};
This assumes that all factories are mutually exclusive. Relaxing this assumption is unlikely to result in well-behaving software. I'd probably make stronger claims in person, hehe. Another alternative would be to return a list of objects.
The below implementation is static for simplicity of demonstration. This can be a problem for multi-threaded environments. It doesn't have to be static, nor do I recommend it should or shouldn't be static, it just is here. It isn't really the subject of the discussion, so I'll leave it at that.
These free functions only act as pass-through functions for this implementation. This lets you unit test the registry or reuse it if you were so inclined.
namespace {
static RuntimeFactoryRegistry* registry = 0;
} // anon
void SetupRuntimeFactory() {
registry = new RuntimeFactoryRegistry;
}
void CleanupRuntimeFactory() {
delete registry;
registry = 0;
}
BaseObject* CreateObjectFromStream(std::istream& is) {
return registry->create(is);
}
void RegisterRuntimeFactory(RuntimeFactory* factory) {
registry->registerFactory(factory);
}
void UnregisterRuntimeFactory(RuntimeFactory* factory) {
registry->unregisterFactory(factory);
}