How to automatically register a class on creation
I was wondering whether a design pattern or idiom exists to automatically register a class type. Or simpler, can I force a method to get called on a class by simply extending a base class?
For example, say I have a base class Animal
and extending classes Tiger
and Dog
, and I have a helper function that prints out all classes that extend Animal
.
So I could have something like:
struct AnimalManager
{
static std::vector<std::string> names;
static void registerAnimal(std::string name) {
//if not already registered
names.push_back(name); }
};
struct Animal
{
virtual std::string name() = 0;
void registerAnimal() { AnimalManager::registerAnimal(name()); }
};
struct Tiger : Animal
{
virtual std::string name() { return "Tiger"; }
};
So basically I would do:
Tiger t;
t.registerAnimal();
This could be worked into a static
function as well. Is there any pattern (like a curiously recursive template) or something like that that can help me achieve this without explicitly having to call the registerAnimal
method.
I want my class Animal
to be extendible in the future and others might forget to call register
, I'm looking for ways to prevent that besides documenting this (which I will anyway).
PS This is just an example, I'm not actually implementing animals.
You can indeed do this using the curiously recursive template idiom. It requires nothing from whoever is extending the class that can't be enforced by the compiler:
template<class T>
struct Animal
{
Animal()
{
reg; //force specialization
}
virtual std::string name() = 0;
static bool reg;
static bool init()
{
T t;
AnimalManager::registerAnimal(t.name());
return true;
}
};
template<class T>
bool Animal<T>::reg = Animal<T>::init();
struct Tiger : Animal<Tiger>
{
virtual std::string name() { return "Tiger"; }
};
In this code, you can only extend Animal
if you specialize it. The constructor forces the static
member reg
to be initialized, which in turn calls the register method.
EDIT: As pointed out by @David Hammen in the comments, you won't be able to have a collection of Animal
objects. However, this can easily be solved by having a non-template class from which the template inherits and use that as a base class, and only use the template for extending.
If you insist every animal should be registered, why not just make name
a parameter of Animal
constructor. Then you can put register issues to Animal
constructor and every derived will have to pass valid name and register:
struct Animal
{
Animal(std::string name){ AnimalManager::registerAnimal(name);}
}
struct Tiger : Animal
{
Tiger():Animal("Tiger"){}
};
This is a typical example where you want to do some sort of bookkeeping when an object is constructed. Item 9 in Scott Meyers "Effective C++" gives an example of this.
Basically you move all the bookkeeping stuff to base class. Derived class explicitly constructs base class and passes the information that is required for Base class construction. For example:
struct Animal
{
Animal(std::string animal_type)
{
AnimalManager::registerAnimal(animal_type);
};
};
struct Dog : public Animal
{
Dog() : Animal(animal_type()) {};
private:
static std::string animal_type()
{
return "Dog";
};
};
Usually I do this with a macro.
Unit test frameworks often employ the technique for registering the tests, e.g. GoogleTest