Sign in to follow this  
aeroz

Base factory method with automatic registration

Recommended Posts

I'm trying to create a factory method for a base class which instantiates a derived class corresponding to the method's parameters.
So I can do:
Base* b = Base::create("DerivedClass");
I read this interesting post on Stackoverflow [url="http://stackoverflow.com/questions/410823/factory-method-implementation-c/534396#534396"]http://stackoverflow...c/534396#534396[/url] (by [url="http://stackoverflow.com/users/842/epatel"]epatel[/url]) but noticed he uses a factory singleton (Factory::instance()->registerCreator()).
Instead of a singleton I used a static varible. Here is my solution:

(C++)
[code]
#include <iostream>
#include <string>
#include <map>
using namespace std;

class Base
{
public:
typedef Base* (*CreateFun)();

static Base* create(string name)
{
FunctionMap::iterator it = creators.find(name);
if (it == creators.end())
return NULL;
return (it->second)();
}
static bool reg(string name, CreateFun fun)
{
creators[name] = fun;
return true;
}
virtual ~Base() {}

private:
typedef map<string, CreateFun> FunctionMap;
static FunctionMap creators;
};

Base::FunctionMap Base::creators;

// this registers a derived class in the factory method of the base class
// it adds a factory function named create_NAME()
// and calls Base::reg() by the help of a dummy static variable to register the function
#define REGISTER( _name ) \
namespace { \
Base* create_ ## _name() { return new _name; } \
static bool _name ## _creator_registered = Base::reg(# _name, create_ ## _name); }

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////

class DerivA: public Base
{
public:
DerivA()
{
cout << "DerivA was created" << endl;
}
};

REGISTER(DerivA)

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////

class DerivB: public Base
{
public:
DerivB()
{
cout << "DerivB was created" << endl;
}
};

REGISTER(DerivB)

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////

int main()
{
Base* b = Base::create("DerivA");
b = Base::create("DerivB");
return 0;
}
[/code]
When you create a new derived class, all you need to do is to use the REGISTER() macro.
Note I don't have a separate factory class. I use the base class for this.
Note also that this code should be slightly modified if split into multiple files (multiple compilation units: no guarantee on the order of initialization of static variables).
Actually I want to use this as a component factory, so I can do something like

[code]GameObject obj;
for each XML component node {
Component* comp = Component::createFromXml(node).
obj.addComponent( comp );
}[/code]
What do you think of this solution?

EDIT: having problems with identation

Share this post


Link to post
Share on other sites
[quote name='aeroz' timestamp='1297870073' post='4774991']
What do you think of this solution?
[/quote]

I think that using static for anything stateful in C++ is a needless overcomplication. Between undefined behavior, coupling of everything from pre-processor down to linker and exposing more language quirks than just about any other concept it's simply a brittle approach and a false optimization - it simply doesn't save anything in the long run and has far-reaching implications on build system (which is what should be really optimized).

IMHO:[code]// factory.h
class Factory {
Entity * make(...);
}

// factory.cpp
#include <foo.h>
#include <bar.h>
#include <baz.h>

Factory::Factory() {
register_type(foo);
register_type(...);
...
}

// main.cpp
int main() {
Factory factory;
}
// or
Factory * factory;
int main() {
factory = new Factory();
}[/code]
As new classes are added or removed, factory alone needs to be recompiled. All registrations are in one place, and the number of lines of code that must be written is *exactly* the same as with various "auto" registration libraries, one line per type.

C++ is static language, so all types are known either at compile time or loaded via exported function from a DLL, the function itself is identical for all types. There is no real benefit towards dynamic registration.

Or, add self-registering handlers to some specific directory, when building, list contents of that directory and construct a .h/.cpp file using sed/awk/perl/... like above. It allows automatic registration while remaining as simple as possible without praying for stars to align so that auto-magic works as intended.

Share this post


Link to post
Share on other sites
I agree your code is simpler. But when you add a new class you don't have everything in one place, you have to search for the factory code and add it there.
Actually there are 2 lines to add: #include "foo.h" and register_type(foo) :D

Thanks for your opinion. I guess I will stick to your method, since it's simpler to understand.

How do others handle this problem?

Share this post


Link to post
Share on other sites
With a big ol' function that registers all the registration-needing stuff.

Automatic registration through global/static variables is fragile and bug-prone. It solves a problem which is not a large problem, by introducing a solution which IS a large problem.

Share this post


Link to post
Share on other sites
I've used a similar automatic-registration technique in production code for years, though using the constructor of meta-class objects that also store shared instance properties. There's only one static method ("Find") and no macro trickery, just a set of "prototype" meta-class instances that seed the class system database at static initialization time. That doesn't mean it's right, though. :)

As Antheus suggests, this relies on compiler- and setting-specific behavior regarding unreferenced object instances. For example, the Visual C++ compiler will remove unreferenced static and const (which are implicitly static) instances since it knows nothing outside the compilation unit can access them. It won't removed unreferenced globals even with whole-program optimization, though there's no absolute guarantee that won't change in the future. According to MSDN, the linker includes or excludes whole COMDATs, and the compiler will package data into a COMDAT only if it's marked __declspec(selectany).

Aside from that, you can't rely on any particular order for static initializations. Specifically, there's no guarantee that your database has been initialized by the time a class attempts to register itself. You might get away with it for a while, but you'll eventually experience the infamous "static initialization order fiasco" first-hand. One solution is the "[url="http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Construct_On_First_Use"]construct on first use[/url]" idiom:

[code]FunctionMap &GetCreatorMap(void)
{
static FunctionMap creators;
return creators;
}
[/code]

Share this post


Link to post
Share on other sites
Thank you for your opinions!

[quote name='kdmiller3' timestamp='1297878499' post='4775035']As Antheus suggests, this relies on compiler- and setting-specific behavior regarding unreferenced object instances.[/quote]
Can you explain that a bit more? Is this really undefined behavior or are the compilers not compliant?
If you take care I don't see any reason for it not working and being portable.
Of course, I agree, you shouldn't take the high risk...

[quote]
Aside from that, you can't rely on any particular order for static initializations. Specifically, there's no guarantee that your database has been initialized by the time a class attempts to register itself. You might get away with it for a while, but you'll eventually experience the infamous "static initialization order fiasco" first-hand. One solution is the "[url="http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Construct_On_First_Use"]construct on first use[/url]" idiom:

[code]FunctionMap &GetCreatorMap(void)
{
static FunctionMap creators;
return creators;
}
[/code]
[/quote]
Yes, I noticed that, thanks. (I wrote that the code should be slightly modified if split into multiple files)

Share this post


Link to post
Share on other sites
Basically, the C++ standard only guarantees that a global will be initialized if the global is actually used. If you don't use it, the compiler doesn't have to initialize it.

Share this post


Link to post
Share on other sites
"The difference between theory and practice is that in theory there is no difference between theory and practice and in practice there is." :)

Unless you plan to support an unusual variety of platforms and compilers or require strict language standards compliance, you're not likely to run into too many problems even though the behavior is undefined. In practice, I've found that the static initialization approach has worked fine with Visual C++ 5.0, 6.0, 2002, 2003, 2005, 2008, and 2010 on PC, as well as Visual C++ for Xbox, ProDG for Playstation 2, and CodeWarrior for GameCube. I can't promise it will work on other platforms, but would be genuinely surprised if it didn't.

On the plus side, if it doesn't work, it should [i]consistently[/i] not work. At that point, you can fall back to registering your classes explicitly.

Share this post


Link to post
Share on other sites
[quote name='kdmiller3' timestamp='1297893503' post='4775132']
In practice, I've found that the static initialization approach has worked fine with Visual C++ 5.0, 6.0, 2002, 2003, 2005, 2008, and 2010...
[/quote]
...unless you put the code in a static library in which case MSVC will consistently strip it out.

Share this post


Link to post
Share on other sites
[quote name='SiCrane' timestamp='1297893619' post='4775134']
...unless you put the code in a static library in which case MSVC will consistently strip it out.
[/quote]

It's definitely another gotcha to watch out for, but makes perfect sense when you think about it. A static library just a collection of things the compiler can import if it wants. It doesn't have much of an identity of its own.

Dynamic link libraries, on the other hand, have their own global construction phase when loaded. I used that feature to good effect on my side project, though I had to add the corresponding "unregister" machinery to handle the global destruction phase when unloaded. (I got burned by that one the first time I restarted a level with an associated DLL. The component registry was full of references to now-unloaded objects, and hilarity ensued.)

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this