C++: How to create a collection of classes at startup

1k Views Asked by At

I'm using C++ 11. I have N source files each containing a class that inherits from a common base class. At startup, I want each class to register itself into a collection. The registration is to include 1) data necessary for identifying the purpose of the class, 2) a class factory for creating instances of the class. The number of source files is unknown. What is the pattern for doing this? The solution needs to be cross-platform compatible with Visual Studio 2013, gcc and others.

3

There are 3 best solutions below

0
BSalita On BEST ANSWER

Using @Pawel's suggestion, I've created an example. The goal is to create a registration process that collects a list of classes, each registration having a class factory and arbitrary data. Specifically, this example registers Mime classes into a std::map. The registration entry holds a vector of content type strings, and a class factory for creating the Mime class.

In each source file, I use a one liner to register the class. A vector of content types is passed to specify the class's supported types. Every class inherits from a base class named Mime (not shown).

RegisterTypes<Swagger> swagger(vector<const char *>({ "application/swagger+json" }));

In a header file, define a struct to contain a registration entry. Two initializers are needed, a vector of content types, and the class factory (implemented as a lamda).

struct Registry
{
public:
    Registry() {} // initializer below forces this initializer to be needed
    Registry(vector<const char *> ct, function<Mime*(void)> cf) : contentTypes(ct), classFactory(cf) {}
    vector<const char *> contentTypes;
    function<Mime*(void)> classFactory;
};

In a header file, extern a std::map which will contain the registrations. Each map entry consists of a key and a registration struct. The actual definition is in a .cpp file (not shown). The registry uses a struct so it can hold multiple values.

extern std::map<const string, struct Registry> Mimes;

In the header file, define the registration class. Each instantiation of the registration class will create a registration entry in a std::map. In this example, the registration class creates a registration entry consisting of a key (class name), a vector of supported content types, and a class factory. The class factory creates instances of the class and is implemented as a lamda. Every registered class has a common base class of Mime.

template<class T> class RegisterTypes
{
public:
    RegisterTypes(vector<const char *> contentTypes)
    {
        Mimes[typeid(T).name()] = { contentTypes, [](void) -> Mime * { return (Mime *)(new T()); } }; // requires a matching explicit initializer
    }
};
1
Paweł Stawarz On

First things first: keep in mind this is only possible if the classes are derived from a single Base class, because you can store only one type of object per vector instance.

And when it comes to the solution... you can declare an object in the corresponding *.cpp file (mark it extern in the *.h file):

// SomeClass.h
// <-- class declaration goes here
extern SomeClass someObj;

// SomeClass.cpp
SomeClass someObj;

And add it to the vector inside the constructor:

SomeClass(){
   myVector.push_back(*this);
}

Note that myVector needs to be visible in this scope.

Your myVector should be populated after you include the SomeClass.h file with one instance of SomeClass. Note that if the populating is done in the base class, you don't have to do it in every subsequent child class, because the base constructor gets called anyhow.

0
Eli Algranti On

Sounds like the Observer Pattern. There may be a better pattern depending on what you want to do with this list of classes.

Cross compatibility would depend entirely on your implementation, but should not posse problems.