Derived class discovery at compile time

The RegisteredClass class is a template class that allows to register derived classes into a map. This allows to instaniate derived classes from the base class by its name. This mechanisem is similar to OpenFOAMs runtime selection mechanism. The classes are registered at compile time via static member initialization. Additional explanation can be found at: here and here .

This approach allows to create a plugin architecture where now a derived class can be loaded at runtime. Additionally, it simplifies the runtime selection of a specific class. The derived classes can be created by providing its name, as shown below:

std::unique_ptr<baseClass> derivedClass =
    baseClass::create("Name of derivedClass", "Additional arguments",...);
template<typename DerivedClass, typename BaseClass, StdFunction CreateFunction>
class RegisteredClass

Template struct for registering a derived class with a base class.

This struct provides a mechanism for registering a derived class with a base class.

Template Parameters:
  • DerivedClass – The derived class to be registered.

  • BaseClass – The base class that the derived class inherits from.

  • CreateFunction – The function pointer type for creating an instance of the derived class.

Public Functions

inline RegisteredClass()

Constructor for the RegisteredClass struct.

reg needs to be called in the constructor to ensure that the class is registered.

Public Static Functions

static inline bool init()

Initializes the registration of the derived class with the base class.

This function registers the derived class with the base class by calling the BaseClassRegistry::registerClass() function with the derived class’s name and create function.

Returns:

True if the registration is successful, false otherwise.

Public Static Attributes

static bool reg = NeoFOAM::RegisteredClass<DerivedClass, BaseClass, CreateFunction>::init()

Static flag indicating if the class has been registered.

Template specialization for registering a derived class with a base class in NeoFOAM.

This template class is used to register a derived class with a base class in NeoFOAM. It provides a static member variable reg which is initialized to true when the class is instantiated

Template Parameters:
  • DerivedClass – The derived class to be registered.

  • BaseClass – The base class with which the derived class is registered.

  • CreateFunction – The function pointer type for creating an instance of the derived class.

The BaseClassRegistry class is a template class that manages the registration of the derived classes and stores the map of the registered classes. The map associates a function with a string that is used to create the derived class.

template<typename BaseClass, StdFunction CreateFunction>
class BaseClassRegistry

Template struct for managing class registration.

This struct provides a mechanism for registering classes with a given base class and a create function. It maintains a map of class names to create functions, allowing for dynamic class instantiation.

Template Parameters:
  • BaseClass – The base class type.

  • CreateFunction – The type of the create function.

Subclassed by NeoFOAM::finiteVolume::cellCentred::VolumeBoundaryFactory< ValueType >

Public Static Functions

static inline bool registerClass(const std::string name, CreateFunction createFunc)

Registers a class with the given name and create function.

Parameters:
  • name – The name of the class.

  • createFunc – The create function for the class.

Throws:

std::runtime_error – if the class name already exists in the map.

Returns:

true if the class was successfully registered, false if the class name already exists in the map.

static inline size_t size()

Gets the number of registered classes.

Returns:

The number of registered classes.

static inline auto &classMap()

A static method that provides access to a map of creation functions.

This method returns a reference to a static unordered map. The map’s keys are strings, representing class identifiers, and the values are creation functions for those classes. This design ensures that the classMap is initialized

Returns:

A reference to the static unordered map of class identifiers to creation functions.

Usage

The following example shows how to use the RegisteredClass and BaseClassRegistry to automatically register derived classes. (details see test_RegisterClass.cpp).

The createFunction returns a std::unique_ptr to its base class and takes an argument list specified by the class interface. Furthermore, the BaseClassRegistry handles the registration and storage of all classes that have been registered.

// forward declaration so we can use it to define the create function and the class manager
class TestBaseClass;

// define the create function use to instantiate the derived classes
using createFunc = std::function<std::unique_ptr<TestBaseClass>(std::string, double)>;

// define the class manager to register the classes
using TestBaseClassManager = NeoFOAM::BaseClassRegistry<TestBaseClass, createFunc>;

We derive the base class from the TestBaseClassManager and define the interface that the derived classes must implement. We also define a template alias TestBaseClassReg that will be used to register the derived classes. The create function will be used to instantiate the derived classes based on the name provided. The RegisteredClass function will be used to register the derived classes. The derived classes will implement the interface functions testString and testValue.

// interface that needs to be implemented by the derived classes
class TestBaseClass : public TestBaseClassManager
{
public:


    template<typename derivedClass>
    using TestBaseClassReg = NeoFOAM::RegisteredClass<derivedClass, TestBaseClass, createFunc>;

    // create function to instantiate the derived classes
    static std::unique_ptr<TestBaseClass>
    create(const std::string& name, std::string testString, double testValue)
    {
        try
        {
            auto func = classMap.at(name);
            return func(testString, testValue);
        }
        catch (const std::out_of_range& e)
        {
            std::cerr << "Error: " << e.what() << std::endl;
            return nullptr;
        }
    }


    template<typename derivedClass>
    bool registerClass()
    {
        return TestBaseClassReg<derivedClass>::reg;
    }

    virtual ~TestBaseClass() = default;

    // interface that needs to be implemented by the derived classes
    virtual std::string testString() = 0;

    virtual double testValue() = 0;

    // ...

};

The derived classes is registered using the RegisteredClass function. The derived classes implements the interface functions testString and testValue. The create function is used to instantiate the derived classes based on the name provided in the name function.

class TestDerivedClass : public TestBaseClass
{

public:

    // the constructor is used to register the class
    TestDerivedClass(std::string name, double test)
        : TestBaseClass(), testString_(name), testValue_(test)
    {
        registerClass<TestDerivedClass>(); // register the class
    }

    // must be implemented by the derived classes to register the class
    static std::unique_ptr<TestBaseClass> create(std::string name, double test)
    {
        return std::make_unique<TestDerivedClass>(name, test);
    }

    // must be implemented by the derived classes to register the class
    static std::string name() { return "TestDerivedClass"; }

    virtual std::string testString() override { return testString_; };

    virtual double testValue() override { return testValue_; };

private:

    std::string testString_;
    double testValue_;
};

After the classes have been defined, the create function can be used to instantiate the derived classes based on the name provided.

std::unique_ptr<TestBaseClass> testDerived =
    TestBaseClass::create("TestDerivedClass", "FirstDerived", 1.0);