Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can one design a base class, so it knows about all the "derived" classes, at run time?

Tags:

c++

Normally, if you know all the types you intend to create before hand, you can just do something like this:

typedef enum{
    BASE_CREATURE_TYPE = 0,
    ANIMAL_CREATURE_TYPE,
    ...
}CREATURE_TYPES

But this becomes tedious, because each time you create a new class, you need to update the enum. Also, the CREATURE_TYPES is still just items in an enum - how to tie to an actual class ?

I was wondering if there was some way, I could just write the classes, and at run time, without actually instantiating an object, create a set containing all the types.

Is this possible in C++? In Java there is something called "static blocks", which are executed when the class is loaded by the JVM.

EDIT: This question is not about static blocks - It is just an example - I am wondering if there is some way, that I can execute a method or block of code so I know what classes exist at runtime, without actually creating an object

EDIT: I meant set of all types, not "maps", so I can create an object of each type, without having to maintain a list.

EDIT: The reason I want this, is because I am trying to create a function that can call methods on all derived classes that are part of the application. For example, say I have several classes which all derive from class Foo, and have a balls():

Foo{
   balls();
}

Boo : public Foo{
   balls();
}

Coo: public Foo{
   balls():
}

At run time, I would like to know about all the derived classes so I can call:

DerivedClass:balls();

EDIT: Note, that I do not need to know about all the members of each derived class, I just want to know what all the derived classes are, so I can call balls(), on each of them.

EDIT: This question is similar: How to automatically register a class on creation

But unfortunately, he is storing an std::string(). How does one refer to the actual class ?

EDIT: In Smeehey's answer below, in the main method, how would I actually create an instance of each class, and call both static and non-static methods ?

like image 661
Rahul Iyer Avatar asked Jun 10 '16 11:06

Rahul Iyer


People also ask

How do you make a base class derived class?

A derived class can access all the non-private members of its base class. Thus base-class members that should not be accessible to the member functions of derived classes should be declared private in the base class. Constructors, destructors and copy constructors of the base class.

How do you identify a base class and a derived class?

1. A base class is an existing class from which the other classes are derived and inherit the methods and properties. A derived class is a class that is constructed from a base class or an existing class.

Which class is used to design the base classes?

Which class is used to design the base class? Explanation: Abstract class is used to design base class because functions of abstract class can be overridden in derived class hence derived class from same base class can have common method with different implementation, hence forcing encapsulation.

Why would one create a base class object with reference to the derived class?

One reason for this could be that BaseClass is abstract (BaseClasses often are), you want a BaseClass and need a derived type to initiate an instance and the choice of which derived type should be meaningful to the type of implementation.


2 Answers

You could create a static registry for all your classes, and use a couple of helper macros to register new types within it. Below is a basic working demonstration, which creates 2 derived classes from Base. To add new classes you just use the two macros shown - one inside and one outside the class. Note: the example is very bare-bones and doesn't concern itself with things like checking for duplicates or other error conditions to maximise clarity.

class BaseClass
{
};

class Registry
{
public:
    static void registerClass(const std::string& name, BaseClass* prototype)
    {
        registry[name] = prototype;    
    }

    static const std::map<std::string, BaseClass*>& getRegistry() { return registry; };

private:
    static std::map<std::string, BaseClass*> registry;
};

std::map<std::string, BaseClass*> Registry::registry;

#define REGISTER_CLASS(ClassType) static int initProtoType() { static ClassType proto; Registry::registerClass(std::string(#ClassType), &proto); return 0; } static const int regToken;
#define DEFINE_REG_CLASS(ClassType) const int ClassType::regToken = ClassType::initProtoType(); 

class Instance : public BaseClass
{
    REGISTER_CLASS(Instance)
};

DEFINE_REG_CLASS(Instance)

class OtherInstance : public BaseClass
{
    REGISTER_CLASS(OtherInstance)
};

DEFINE_REG_CLASS(OtherInstance)

int main()
{
    for(auto entry : Registry::getRegistry())
    {
        std::cout << entry.first << std::endl;
    }
    return 0;
}

The above registers prototypes of the derived classes, which could be used for copy-constructing other instances for example. As an alternative, requested by the OP, you can have a system where factory methods are registered instead of prototypes. This allows you to create instances using a constructor with any particular signature, rather than the copy constructor:

class BaseClass
{
};

class Registry
{
public:
    using factoryMethod = BaseClass* (*)(int a, int b, int c);

    static void registerClass(const std::string& name, factoryMethod meth)
    {
        registry[name] = meth;    
    }

    static BaseClass* createInstance(const std::string& type, int a, int b, int c)
    {
        return registry[type](a, b, c);
    }

    static const std::map<std::string, factoryMethod>& getRegistry() { return registry; };

private:
    static std::map<std::string, factoryMethod> registry;
};

std::map<std::string, Registry::factoryMethod> Registry::registry;

#define REGISTER_CLASS(ClassType) static BaseClass* createInstance(int a, int b, int c)     \
                                  {                                                         \
                                      return new ClassType(a,b,c);                          \
                                  }                                                         \
                                  static int initRegistry()                                 \
                                  {                                                         \
                                       Registry::registerClass(                             \
                                           std::string(#ClassType),                         \
                                           ClassType::createInstance);                      \
                                       return 0;                                            \
                                  }                                                         \
                                  static const int regToken;                                \

#define DEFINE_REG_CLASS(ClassType) const int ClassType::regToken = ClassType::initRegistry(); 

class Instance : public BaseClass
{
    Instance(int a, int b, int c){}

    REGISTER_CLASS(Instance)
};

DEFINE_REG_CLASS(Instance)

class OtherInstance : public BaseClass
{
    OtherInstance(int a, int b, int c){}

    REGISTER_CLASS(OtherInstance)
};

DEFINE_REG_CLASS(OtherInstance)

int main()
{
    std::vector<BaseClass*> objects;
    for(auto entry : Registry::getRegistry())
    {
        std::cout << entry.first << std::endl;
        objects.push_back(Registry::createInstance(entry.first, 1, 2, 3));
    }
    return 0;
}
like image 183
Smeeheey Avatar answered Sep 19 '22 17:09

Smeeheey


Use the CRTP design with interface for common "ancestor":

#include <vector>
#include <iostream>

/* Base */
struct IBase
{
    virtual void balls() = 0;
    virtual IBase *clone() const = 0;

private:
    static std::vector<IBase const *> _Derived;

public:
    static void
    create_all(void)
    {
    std::cout << "size: " << _Derived.size() << "\n";
        for (IBase const *a : _Derived)
        {
            IBase *new_object(a->clone());
            (void)new_object; // do something with it
        }
    }
};

std::vector<IBase const *> IBase::_Derived;

/* Template for CRTP */
template<class DERIVED>
class Base : public IBase
{
    static bool       created;
    static Base const *_model;

public:
    Base(void)
    {
        if (not created)
        {
            _Derived.push_back(this);
            created = true;
        }
    }
};

template<class DERIVED>
bool Base<DERIVED>::created = false;
template<class DERIVED>
Base<DERIVED> const *Base<DERIVED>::_model = new DERIVED;

/* Specialized classes */
struct Foo1 : public Base<Foo1>
{
    IBase *clone() const
    {
        std::cout << "new Foo1\n";
        return new Foo1(*this);
    }
    void balls() {}
};


struct Foo2 : public Base<Foo2>
{
    IBase *clone() const
    {
        std::cout << "new Foo2\n";
        return new Foo2(*this);
    }
    void balls() {}
};


int main(void)
{
    Foo1    a;
    IBase::create_all();
}

I tried this solution, but I do not know why the static Base const *_model; is not created when running the program.

like image 40
Boiethios Avatar answered Sep 22 '22 17:09

Boiethios