Victor Bazarov wrote:
Well, I've re-read your original post and can't claim full understanding
of the design intent, sorry. Perhaps a complete program that does what
you describe (even if it fails to compile or link) would be in order.
All right, here's a complete (a bit long) example:
// --- Base.h ---
#ifndef Base_H
#define Base_H
#include <map>
#include <iostream>
template <typename T>
class Base
{
public:
typedef Base* (*PCreatorFunction)();
static void RegisterCreator(int id, PCreatorFunction f)
{
creators[id] = f;
}
static Base* Create(int id)
{
Derived1<T>::Dummy();
Derived2<T>::Dummy();
PCreatorFunction cf = creators[id];
return cf();
}
virtual void F() { std::cout << "Base::F()"; }
protected:
typedef std::map<int, PCreatorFunctionCreatorMap;
static CreatorMap creators;
};
template <typename T>
typename Base<T>::CreatorMap Base<T>::creators;
template <typename D, typename T, int id>
struct DerivedInitializer
{
DerivedInitializer()
{
Base<T>::RegisterCreator(id, &D::Create);
}
void Dummy() {}
};
#endif
// --- Derived1.h ---
#ifndef Derived1_H
#define Derived1_H
#include "Base.h"
template <typename T>
class Derived1:
public Base<T>
{
public:
static void Dummy() { initializer.Dummy(); }
static Base<T>* Create() { return new Derived1<T>; }
virtual void F() { std::cout << "Derived1::F()"; }
protected:
static DerivedInitializer<Derived1<T>, T, 1initializer;
};
template <typename T>
DerivedInitializer<Derived1<T>, T, 1Derived1<T>::initializer;
#endif
// --- Derived2.h ---
#ifndef Derived2_H
#define Derived2_H
#include "Base.h"
template <typename T>
class Derived2:
public Base<T>
{
public:
static void Dummy() { initializer.Dummy(); }
static Base<T>* Create() { return new Derived2<T>; }
virtual void F() { std::cout << "Derived2::F()"; }
protected:
static DerivedInitializer<Derived2<T>, T, 2initializer;
};
template <typename T>
DerivedInitializer<Derived2<T>, T, 2Derived2<T>::initializer;
#endif
// --- main.cpp ---
#include "Base.h"
#include "Derived1.h"
#include "Derived2.h"
int main(int argc, char *argv[])
{
int id;
id = 1;
Base<int>* bi1 = Base<int>::Create(id);
bi1->F(); // Derived1
Base<float>* bf1 = Base<float>::Create(id);
bf1->F(); // Derived1
id = 2;
Base<int>* bi2 = Base<int>::Create(id);
bi2->F(); // Derived2
return 0;
}
// ---
This does work, but the Dummy() calls in Base::Create are ugly, because
that list needs to be extended whenever a new subclass (say,
Derived3<T>: public Base<T>) is added. The system cannot be cleanly
extended without modifying existing code.
If these were normal, non-template classes, then this would not be
necessary, new subclasses could be added without modifying Base.
As Jens suggested, I could place those Dummy() calls (or any other
references that force the instantiation of the derived initializer)
somewhere else, but that wouldn't really solve the problem. Actually,
if those are not in Base, then things are even worse, as instead of
referencing DerivedX<T>, I need to reference DerivedX<int>,
DerivedX<float>, etc. separately. And if a new Derived is added, I need
to revisit all the places where such references are written, and extend
the list.
(Note that Base is used with implicit instantiation. Having the client
to explicitly instantiate the subclasses is.. at least strange.)
Also note that the information about what instances of, say,
Derived1::initializer should be created, are almost there. The compiler
knows what instances of Base::Create are created (<intand <floatin
the above example). I'd like to tell it to create the same instances of
the subclass initializers. Actually that's what the Dummy() calls do,
it's just that they are at a very wrong place.
Maybe I'm worrying too much over this; after all, adding a new line to
Base::Create for each subclass isn't that much. But still, modifying
existing code because of adding a new subclass somehow feels wrong.
Also, if this pattern works for non-templates, it should work for
templates as well.
You modify something when adding a subclass, you might as well modify
something else... For example, the "config variable" that your class
uses to find the proper "implemenation" to be instantiated. If you are
adding another implementation, then the 'config' has to know about it,
no?
Not necessarily, see the example. The config var (called id in the
example) is a simpe integer, and all subclasses are assigned an integer
identifying them. When using the Base as a factory, we just give it a
subclass id, and it can find the right one -- as long as subclasses are
properly registered at startup.
Anyway, thanks for all the answers.
Imre