Here is an example using CRTP which dynamically registers a set of generators at runtime (c-startup). A singleton factory pattern is used to dynamically create new instances of data at runtime according to selected algorithms defined in the implementation.
The design is split into two parts (namespace util). The first part consists of the PseudoRandomGenerator Base and Template class and Factory for all generators. The second part is for your implementation of the various data generation algorithms. Each of the implementation classes in the second part can be split apart into separate files (3 in our case).
Working example 1
#include <iostream>
#include <string>
#include <map>
#include <memory>
namespace PseudoRandomGeneratorTypes { enum : int { Type1, Type2, Type3 }; }
namespace util {
template<size_t SIZE>
struct __Data {
int a;
};
using Data = __Data<10>;
class PseudoRandomGenerator {
protected:
PseudoRandomGenerator() {}
public:
auto getType() const { return _type; }
virtual Data generate() const = 0;
protected:
int _type;
};
template<int PRGType, typename PRGImpl>
class PRGTmpl : public PseudoRandomGenerator {
public:
static PseudoRandomGenerator* CreatePtr() {
return new PRGImpl();
}
static const int TYPE;
protected:
PRGTmpl() { _type = TYPE; }
};
class PseudoRandomGeneratorFactory {
public:
typedef PseudoRandomGenerator* (*psg)();
static auto get()
{
static PseudoRandomGeneratorFactory fact;
return &fact;
}
auto Register(int id, psg m)
{
_map[id] = m;
return id;
}
auto Create(int id)
{
return _map[id]();
}
private:
PseudoRandomGeneratorFactory() {}
~PseudoRandomGeneratorFactory() {}
std::map<int, psg> _map;
};
template <int arbitaryPRGType, typename arbitaryPRGImpl>
const int PRGTmpl<arbitaryPRGType, arbitaryPRGImpl>::TYPE = PseudoRandomGeneratorFactory::get()->Register(arbitaryPRGType, &PRGTmpl<arbitaryPRGType, arbitaryPRGImpl>::CreatePtr);
}
namespace util {
class PRGType1 : public PRGTmpl < PseudoRandomGeneratorTypes::Type1, PRGType1 > {
public:
virtual Data generate() const override final { return Data{ 111 }; }
};
template class PRGTmpl < PseudoRandomGeneratorTypes::Type1, PRGType1 >;
class PRGType2: public PRGTmpl < PseudoRandomGeneratorTypes::Type2, PRGType2 > {
public:
virtual Data generate() const override final { return Data{ 222 }; }
};
template class PRGTmpl < PseudoRandomGeneratorTypes::Type2, PRGType2 >;
class PRGType3 : public PRGTmpl < PseudoRandomGeneratorTypes::Type3, PRGType3 > {
public:
virtual Data generate() const override final { return Data{ 333 }; }
};
template class PRGTmpl < PseudoRandomGeneratorTypes::Type3, PRGType3 >;
}
using namespace util;
using namespace std;
int main()
{
auto rng1 = unique_ptr<PseudoRandomGenerator>(PseudoRandomGeneratorFactory::get()->Create(PseudoRandomGeneratorTypes::Type1));
auto rng2 = unique_ptr<PseudoRandomGenerator>(PseudoRandomGeneratorFactory::get()->Create(PseudoRandomGeneratorTypes::Type2));
auto rng3 = unique_ptr<PseudoRandomGenerator>(PseudoRandomGeneratorFactory::get()->Create(PseudoRandomGeneratorTypes::Type3));
cout << rng1->generate().a << endl;
cout << rng2->generate().a << endl;
cout << rng3->generate().a << endl;
}
Furthermore, if you only need a single instance at a time and wish to not use the heap the CreatePtr() func. can be substituted with the following.
static PseudoRandomGenerator* GetPtr() {
static PRGImpl _m;
_m = PRGImpl();
return &_m;
}
The specialisation changes to:
template <int arbitaryPRGType, typename arbitaryPRGImpl>
const int PRGTmpl<arbitaryPRGType, arbitaryPRGImpl>::TYPE = PseudoRandomGeneratorFactory::get()->Register(arbitaryPRGType, &PRGTmpl<arbitaryPRGType, arbitaryPRGImpl>::GetPtr);
and usage pattern changes to:
auto rng1 = PseudoRandomGeneratorFactory::get()->Create(PseudoRandomGeneratorTypes::Type1);
auto rng2 = PseudoRandomGeneratorFactory::get()->Create(PseudoRandomGeneratorTypes::Type2);
auto rng3 = PseudoRandomGeneratorFactory::get()->Create(PseudoRandomGeneratorTypes::Type3);
In the second example we can use a plain C style array instead of a map to avoid dynamic memory allocation.
Working example 2
Adapted from
Dynamically register constructor methods in an AbstractFactory at compile time using C++ templates