tags:

views:

239

answers:

4

Suppose I have some per-class data: (AandB.h)

class A
{
public:
   static Persister* getPersister();
}

class B
{
public:
   static Persister* getPersister();
}

... and lots and lots more classes. And I want to do something like:

persistenceSystem::registerPersistableType( A::getPersister() );
persistenceSystem::registerPersistableType( B::getPersister() );
...
persistenceSystem::registerPersistableType( Z::getPersister() );

... for each class.

My question is: is there a way to automate building a list of per-type data so that I don't have to enumerate each type in a big chunk (as in the above example)?

For example, one way you might do this is: (AutoRegister.h)

struct AutoRegisterBase
{
   virtual ~AutoRegisterBase() {}
   virtual void registerPersist() = 0;
   static AutoRegisterBase*& getHead()
   {
      static AutoRegisterBase* head= NULL;
      return head;
   }

   AutoRegisterBase* next;
};

template <typename T>
struct AutoRegister : public AutoRegisterBase
{
   AutoRegister() { next = getHead(); getHead() = this; }

   virtual void registerPersist()
   {
       persistenceSystem::registerPersistableType( T::getPersister() );
   }
};

and use this as follows: (AandB.cxx: )

static AutoRegister<A> auto_a;
static AutoRegister<B> auto_b;

Now, after my program starts, I can safely do: (main.cxx)

int main( int, char ** )
{
    AutoRegisterBase* p = getHead();
    while ( p )
    {
        p->registerPersist();
        p = p->next;
    }
    ...
}

to collect each piece of per-type data and register them all in a big list somewhere for devious later uses.

The problem with this approach is that requires me to add an AutoRegister object somewhere per type. (i.e. its not very automatic and is easy to forget to do). And what about template classes? What I'd really like is for the instantiation of a template class to somehow cause that class to get automatically registered in the list. If I could do this I would avoid having to have the user of the class (rather than the author) to remember to create a:

static AutoRegister< SomeClass<X1> > auto_X1;
static AutoRegister< SomeClass<X2> > auto_X2;
...
etc....

for each template class instantiation.

For FIW, I suspect there's no solution to this.

+2  A: 

Register each template at run-time in the constructor. Use a static variable per template to check if the type has already been registered. The following is a quickly hacked together example:

#include <iostream>
#include <vector>

using namespace std;

class Registerable {
    static vector<Registerable *> registry_;

public:
    static void registerFoo(Registerable *p)
    {
        registry_.push_back(p);
    }

    static void printAll()
    {
        for (vector<Registerable *>::iterator it = registry_.begin();
             it != registry_.end(); ++it)
            (*it)->print();
    }

    virtual void print() = 0;
};

vector<Registerable *> Registerable::registry_;

template <typename T>
class Foo : public Registerable {
    static bool registered_;

public:
    Foo()
    {
        if (!registered_) {
            registerFoo(this);
            registered_ = true;
        }
    }

    void print()
    {
        cout << sizeof (T) << endl;
    }
};

template <typename T> bool Foo<T>::registered_ = false;

int
main(int argc, char *argv[])
{
    Foo<char> fooChar;
    Foo<short> fooShort;
    Foo<int> fooInt;

    Registerable::printAll();

    return 0;
}

It should output the size of each template parameter in the order the classes were instantiated:

1
2
4


This version removes the registration code from each constructor and puts it in a base class.

#include <iostream>
#include <vector>

using namespace std;

class Registerable {
    static vector<Registerable *> registry_;

public:
    static void registerFoo(Registerable *p)
    {
        registry_.push_back(p);
    }

    static void printAll()
    {
        for (vector<Registerable *>::iterator it = registry_.begin();
             it != registry_.end(); ++it)
            (*it)->print();
    }

    virtual void print() = 0;
};

vector<Registerable *> Registerable::registry_;

template <typename T>
class Registerer : public Registerable {
    static bool registered_;

public:
    Registerer(T *self)
    {
        if (!registered_) {
            registerFoo(self);
            registered_ = true;
        }
    }
};

template <typename T> bool Registerer<T>::registered_ = false;

template <typename T>
class Foo : public Registerer<Foo<T> > {
public:
    Foo() : Registerer<Foo<T> >(this) { }

    void print()
    {
        cout << sizeof (T) << endl;
    }
};

int
main(int argc, char *argv[])
{
    Foo<char> fooChar;
    Foo<short> fooShort;
    Foo<int> fooInt;

    Registerable::printAll();

    return 0;
}

I added an example of another non-template class using the registry. So, the final output would be:

foo: 1
foo: 2
foo: 4
bar
Judge Maygarden
A: 

The Registerable solution is a neat idea, but has a couple of issues. Ideally, I'd like to not add code to the constructor:

  1. Because it relies on calling the constructor in order to register the type, it's a little haphazard about what gets registered and what doesn't. For things like persistence, I may never call the constructor of a particular type before using the list, but I may need the type's data in the list in order to know how to un-persist an object in a file.

  2. There's runtime cost during the constructor call. I'd like to front load the time cost and not pay the cost many times. If I had a vector of these objects and resized the vector I'd pay the time-cost each time the copy constructor was called.

The only additional copying overhead would be checking one conditional to see if it has been registered, and you can get around that with proper copy constructors.
Judge Maygarden
+3  A: 

You can execute something before main once if a instantiation of a template is made. The trick is to put a static data member into a class template, and reference that from outside. The side effect that static data member triggers can be used to call the register function:

template<typename D>
struct automatic_register {
private:
    struct exec_register {
        exec_register() {
            persistenceSystem::registerPersistableType(
                D::getPersister()
            );
        }
    };
    // will force instantiation of definition of static member
    template<exec_register&> struct ref_it { };

    static exec_register register_object;
    static ref_it<register_object> referrer;
};

template<typename D> typename automatic_register<D>::exec_register 
    automatic_register<D>::register_object;

Derive the class you want to be auto-registered from automatic_register<YourClass> . The register function will be called before main, when the declaration of referrer is instantiated (which happens when that class is derived from, which will implicitly instantiate that class from the template).

Having some test program (instead of the register function, a function do_it is called):

struct foo : automatic_register<foo> {    
    static void do_it() {
        std::cout << " doit "; 
    } 
}; 

int main() { 
    std::cout << " main "; 
}

Yields this output (as expected):

doit main
Johannes Schaub - litb
A: 

litb's answer looks like a great one. The only main downside for me is the "(void) register_object;" in automatic_register()'s constructor:

  1. I guess this involves an additional memory lookup each time foo's constructor is called. (= speed overhead.)
  2. I'll be curious to know whether very aggressive optimizers remove this lookup.

There are also a couple of strange bits of behavior when I tried it (g++ 4.2.4). In the example below, if I comment out constructors to bar and foo, neither get registered. If both constructors are present, despite the fact that neither constructor is ever called, both classes are registered. Makes no difference if the printout is in the constructor.

Something to do with POD type constructor rules? (I tried to making foo a non-POD type by adding a destructor. Seemed to make no difference.)

If I instantiate either class within main, C++ provides a constructor with calls the base constructor and automatically registers the objects.

#include <iostream>

template<typename D>
struct automatic_register
{
    automatic_register() 
    {
       // reference it
       (void) register_object;
    }
private:
    struct exec_register
    {
       exec_register() 
       {
          std::cout << "Register: " << D::getName() << std::endl;
       }
    };

    static exec_register register_object;
};

template<typename D>
typename automatic_register<D>::exec_register automatic_register<D>::register_object;


class foo : automatic_register<foo>
{ 
public:
#if 0
   foo() { std::cout << " foo::foo()"; } 
#endif

   virtual void x() {}
   static char const* getName()
   {
      return "foo";
   } 

   int i;
}; 

class bar : automatic_register<bar>
{ 
public:
#if 0
   bar() { std::cout << " bar::bar()";  } 
#endif

   static char const* getName()
   {
      return "bar";
   } 

   foo f;
}; 

int main(int, char **t )
{
    std::cout << " main "; 
}
yes, as i write in my answer, you have to always write constructors - even if they are empty. otherwise, the classes are only registered if they're used. And don't worry about that "(void) register_object;". it's certainly optimized out by any sane compiler. what matters is that you reference it.
Johannes Schaub - litb
I've found another way to trigger the call - using a template having a reference parameter that's used when instantiating the static member's declaration (no definition needs to be instantiated of it).
Johannes Schaub - litb