5

Basically, I want to automatically register object creator functions with an object factory for a bunch of classes defined across many header files.

The top answer to this post, provides a solution -- but it doesn't fit my constraints.

I'm working on an existing code base. For the classes I need to register, there is already a macro following the class declarations that takes the class as a parameter. If I were able to extend the existing macro definition to also do registration, then it would save a lot time because no existing code would have to be changed.

The closest solution I've been able to come up with is creating a macro that defines a template specialization of a method that registers that object, then calls the previously defined template specialization method -- thus chaining all the register calls. Then, when I want to register all classes, I just call the most recently defined specialization and it registers everything in reverse order of #include appearance.

Below, I've posted a simple working example that shows my solution thus far.

The only caveat is that I have no way of automatically keeping track of the last registered type to call in the chain. So I keep redefining the #define LAST_CHAIN_LINK to be the most recently specialized typename. This means that I'd have to add two lines of #undef/#define after every existing macro call -- I'd really like to avoid that.

The main question: In the code below, is there any way to define the REGISTER_CHAIN macro to work without using the LAST_CHAIN_LINK #undef/#define code too?

If only it were possible to redefine the LAST_CHAIN_LINK token inside the REGISTER_CHAIN method...

My guess is some solution is possible using the __COUNTER__ preprocessor feature, but that is not available on one of the target platforms (OS X using gcc 4.2.x) and thus not an option.

Simplified example (compiles on GNU C++ 4.4.3):

#include <map>
#include <string>
#include <iostream>

struct Object{ virtual ~Object() {} }; // base type for all objects

// provide a simple create function to derived classes
template<class T> struct ObjectT : public Object {
  static Object* create() { return new T(); }
};

struct ObjectFactory {
  // pass in creator function pointer to register it to id
  static Object* create(const std::string& id, Object* (*creator)() = 0) {
    static std::map<std::string, Object* (*)()> creators;
    return creator && (creators[id] = creator) ? 0 : creators.find(id) != creators.end() ? (*creators.find(id)->second)() : 0;
  }
  template<class T = int> struct Register { static void chain() {} };
};


#define LAST_CHAIN_LINK // empty to start

#define REGISTER_CHAIN(T)                               \
  template<> void ObjectFactory::Register<T>::chain()   \
  {                                                     \
    ObjectFactory::create(#T, T::create);               \
    std::cout << "Register<" << #T << ">::chain()\n";   \
    ObjectFactory::Register<LAST_CHAIN_LINK>::chain();  \
  }

struct DerivedA : public ObjectT<DerivedA> { DerivedA() { std::cout << "DerivedA constructor\n"; } };
REGISTER_CHAIN(DerivedA);
// Can these next two lines be eliminated or folded into REGISTER_CHAIN?
#undef LAST_CHAIN_LINK
#define LAST_CHAIN_LINK DerivedA

struct DerivedB : public ObjectT<DerivedB> { DerivedB() { std::cout << "DerivedB constructor\n"; } };
REGISTER_CHAIN(DerivedB);
// Can these next two lines be eliminated or folded into REGISTER_CHAIN?
#undef LAST_CHAIN_LINK
#define LAST_CHAIN_LINK DerivedB

struct DerivedC : public ObjectT<DerivedC> { DerivedC() { std::cout << "DerivedC constructor\n"; } };
REGISTER_CHAIN(DerivedC);
// Can these next two lines be eliminated or folded into REGISTER_CHAIN?
#undef LAST_CHAIN_LINK
#define LAST_CHAIN_LINK DerivedC

struct DerivedD : public ObjectT<DerivedD> { DerivedD() { std::cout << "DerivedD constructor\n"; } };
REGISTER_CHAIN(DerivedD);
// Can these next two lines be eliminated or folded into REGISTER_CHAIN?
#undef LAST_CHAIN_LINK
#define LAST_CHAIN_LINK DerivedD

int main(void)
{
  // Call last link in the register chain to register all object creators
  ObjectFactory::Register<LAST_CHAIN_LINK>::chain();
  delete ObjectFactory::create("DerivedA");
  delete ObjectFactory::create("DerivedB");
  delete ObjectFactory::create("DerivedC");
  delete ObjectFactory::create("DerivedD");
  return 0;
}

example output:

> g++ example.cpp && ./a.out
Register<DerivedD>::chain()
Register<DerivedC>::chain()
Register<DerivedB>::chain()
Register<DerivedA>::chain()
DerivedA constructor
DerivedB constructor
DerivedC constructor
DerivedD constructor
Community
  • 1
  • 1
McKay.CPP
  • 440
  • 1
  • 3
  • 11
  • What problem is the chaining trying to solve? Why not register factories in a, possibly ordered, global registry? – Georg Fritzsche May 26 '11 at 11:35
  • @Georg The current code uses a global registry as you describe. Ordering does not matter. The problem is its becoming unmaintainable with hundreds of objects across separable sub-projects. Each sub-project is having to maintain its own registry of types. Using the method described above would allow creating executables that could generate objects by just including the headers of types it needs to generate. – McKay.CPP May 26 '11 at 18:14
  • Wait- what exactly in the linked post *doesn't* meet your description? – Puppy May 26 '11 at 21:15
  • @DeadMG I want the implementation via a macro. The linked post solution requires I change the inheritance of each derived class -- which I want to avoid. – McKay.CPP May 27 '11 at 00:54

2 Answers2

3

I find your concept pretty complicated and I'm not sure if it's required. From my point of view your problem can be circumvented when adding the following code:

#include <iostream>
#include <map>
#include <string>

struct Object{}; // Value Object


// provide a simple create function to derived classes
template<class T> struct ObjectT : public Object {

    static Object* create() { return new T(); }
};

struct ObjectFactory {

    std::map<std::string, Object* (*)()> creators_factory;

    static ObjectFactory* instance()
    {
        static ObjectFactory* __self = NULL;
        if (__self == NULL)
            __self = new ObjectFactory();

        return __self;

    }

    template <class T> bool reg(const std::string& id,  Object* (*creator)() )
    {
        creators_factory[id] = creator;
        return true;
    }

    // pass in creator function pointer to register it to id
    static Object* create(const std::string& id) {
        return instance()->creators_factory[id]();
    }

};

#define REGISTER_CHAIN(T) bool isRegistered_##T =  ObjectFactory::instance()->reg<T>(#T, T::create)

struct DerivedA : public ObjectT<DerivedA> { DerivedA() { std::cout << "DerivedA constructor\n"; } };
REGISTER_CHAIN(DerivedA);

struct DerivedB : public ObjectT<DerivedB> { DerivedB() { std::cout << "DerivedB constructor\n"; } };
REGISTER_CHAIN(DerivedB);


struct DerivedC : public ObjectT<DerivedC> { DerivedC() { std::cout << "DerivedC constructor\n"; } };
REGISTER_CHAIN(DerivedC);

struct DerivedD : public ObjectT<DerivedD> { DerivedD() { std::cout << "DerivedD constructor\n"; } };
REGISTER_CHAIN(DerivedD);

int main(void)
{
    // Call last link in the register chain to register all object creators
    //ObjectFactory::Register<LAST_CHAIN_LINK>::chain();
    delete ObjectFactory::create("DerivedA");
    delete ObjectFactory::create("DerivedB");
    delete ObjectFactory::create("DerivedC");
    delete ObjectFactory::create("DerivedD");
    return 0;
}

I hope this helps.

Best regards, Martin

grundprinzip
  • 2,471
  • 1
  • 20
  • 34
  • @gprundprinzip This helps, but the token pasting isRegistered_##T will not work with typenames qualified with namespaces (i.e. REGISTER_CHAIN(Derived::Subclass:A). Unfortunately this is also a requirement because the current macro must be in the global namespace, and all the types in question are in different namespaces. This is where the `__COUNTER__` macro I mentioned would be useful -- but that's not an option. – McKay.CPP May 26 '11 at 18:21
2

Using Martin/ @grundprinzip 's suggestion I've been able to solve my problem. I had to modify his approach a bit to allow registration of classes in namespaces.

Thanks Martin!

But, I have a follow-up question now: Isn't it possible the that compiler will entirely optimize out the static ObjectFactory::Register::creator variable (equivalent to is_Registered_##T in @grundprinzip 's code) -- because no code actually references this value?

If so, then optimizing out the variable will optimize out the initialization...thus breaking what I'm hoping to achieve.

Here is the revised code:

#include <map>
#include <string>
#include <iostream>

struct Object{ virtual ~Object() {} }; // base type for all objects

struct ObjectFactory {
  static Object* create(const std::string& id) { // creates an object from a string
    const Creators_t::const_iterator iter = static_creators().find(id);
    return iter == static_creators().end() ? 0 : (*iter->second)(); // if found, execute the creator function pointer
  }

 private:
  typedef Object* Creator_t(); // function pointer to create Object
  typedef std::map<std::string, Creator_t*> Creators_t; // map from id to creator
  static Creators_t& static_creators() { static Creators_t s_creators; return s_creators; } // static instance of map
  template<class T = int> struct Register {
    static Object* create() { return new T(); };
    static Creator_t* init_creator(const std::string& id) { return static_creators()[id] = create; }
    static Creator_t* creator;
  };
};

#define REGISTER_TYPE(T, STR) template<> ObjectFactory::Creator_t* ObjectFactory::Register<T>::creator = ObjectFactory::Register<T>::init_creator(STR)

namespace A { struct DerivedA : public Object { DerivedA() { std::cout << "A::DerivedA constructor\n"; } }; }
REGISTER_TYPE(A::DerivedA, "A");

namespace B { struct DerivedB : public Object { DerivedB() { std::cout << "B::DerivedB constructor\n"; } }; }
REGISTER_TYPE(B::DerivedB, "Bee");

namespace C { struct DerivedC : public Object { DerivedC() { std::cout << "C::DerivedC constructor\n"; } }; }
REGISTER_TYPE(C::DerivedC, "sea");

namespace D { struct DerivedD : public Object { DerivedD() { std::cout << "D::DerivedD constructor\n"; } }; }
REGISTER_TYPE(D::DerivedD, "DEE");

int main(void)
{
  delete ObjectFactory::create("A");
  delete ObjectFactory::create("Bee");
  delete ObjectFactory::create("sea");
  delete ObjectFactory::create("DEE");
  return 0;
}

produces the correct result:

> g++ example2.cpp && ./a.out
A::DerivedA constructor
B::DerivedB constructor
C::DerivedC constructor
D::DerivedD constructor
McKay.CPP
  • 440
  • 1
  • 3
  • 11
  • Yes, aggressive unused data removal *will* break your code. I've been trying to prevent this from happening on MSVC- without it, it's a trivial story to make something that works. As long as the class is instantiated directly at least once, but *not* in the template code, then it will work. – Puppy May 26 '11 at 21:21
  • You can define the registered variable as a static member variable of the type you are registering. The variable is used at the point of registering the class and thus can never be removed. I did not add this to the example, since you did not define a general interface for the base class. – grundprinzip May 30 '11 at 21:32
  • @grundprinzip In my real-world code that is not an option as some of the types I'm registering are 3dparty code that I cannot modify. Thankfully, my solution above seems to work in all cases without optimizing out the code even in release builds on the platforms I care about. – McKay.CPP May 31 '11 at 04:02