2

I'm trying to engineer this:

  • 200 subclasses [ Derived Classes ]
  • After a subclass is defined, I wont need to edit any other file. [ Decoupled ]
  • Subclass Definition registers itself. [ Definition is Registration ]

What I could do instead:

Create a class [ One big one ]

Create an enum value (or similar) for every functional variant [ Highly Coupled ]

alter behaviour in a switch [ Cumbersome ]

While that would work with its own advantages, I'd like to see if something more polymorphic, decoupled, and Modular is achievable, like this:

class Base { ... };
static QList<Base> s_RegisteredClasses; // Empty
class A : public Base{ ... };
class B : public Base{ ... };
/* s_RegisteredClasses should now have A & B inside of it */

What I've tried involves static constructors, where every base class has a different signature. The initialization works, but I wouldnt know how to register it:

class Register { public: Register() { /* Static Constructor to run any code */ } };

template<class T>
class Base { static inline Register selfRegister{} }; // `Register` initiated

static QList<Base<?>> s_RegisteredClasses; // ? prevents polymorphism
class A : public Base<A>{ ... };
class B : public Base<B>{ ... };
/* Both A & B run `Register()` constructor,
   but how do you add them to a list? */

The self initialization works, registration doesn't.

Should I consider a QList<void *> instead?

The other variant looks like this, with the base class having one signature:

class Register { public: Register() { /* Static Constructor to run any code */ } };

template<class T>
class Base { static inline T selfRegister{} }; // Register initiated only once.

static QList<Base<Register>> s_RegisteredClasses; // but I can now polymorph
class A : public Base<Register>{ ... };
class B : public Base<Register>{ ... };
/* How can I self initialize A & B, adding them to s_RegisteredClasses ? */

I am close to giving up, but I haven't tried registering function pointers yet.

Is a text Macro the appropriate solution here, or that there is another paradigm I am overlooking?


Context

I am designing a Card Game where the instructions are printed in a Pseudo C++ code. I am hoping a subclass would represent a Card looking like this:

Was created a few years ago; might not use the same instructions today but the point is gotten across well enough.

Anon
  • 3,639

2 Answers2

1

Use the power of C++, specifically, CRTP, to implement a registration mixin (Mixin-Based Programming with C++ (Smaragdakis, Batory)).

Your classes then don't even have to inherit (directly) from the same base class (as they would in an abstract class pattern). (Though they can from a base registration class that the CRTP class is derived from.) (Because part of the power of C++ is multiple inheritance of behavior, as well.)

namespace Details {
  class Base_Registrator { ... };
}

template <class C> class Registrator : public Details::Base_Registrator { ... ... { use C here to get useful stuff from it } ... };

class MyCardishFoo : public Whatever, private Registrator<MyCardishFoo> { };

[This answer in dedicated in loving memory to ATL: the greatest and most powerful and most ahead of its time C++ mixin library of all time.]

davidbak
  • 762
  • 1
  • 7
  • 10
0

You basically need a base class, a registration class, and a template class. From there, your derived classes will be neatly registered and defined without any extra effort on your part.

1: Abstract Base Class + Pointer List:

class Base
{
public:
    Base() {}
    virtual ~Base() {}
    virtual int supply() const = 0;
};
static QList<Base*> s_Registered;

Notes: Here, you should tightly define the functions you want used, preferably making them abstract to force your definitions in your derived class. supply() for example may be used by me to control the number of objects that can be created from this.

2: Static Constructor

class Registrar
{
public:
    Registrar( Base *c )
    {
        s_Registered << c;
    }
};

Notes: On definition of a class containing a static member for Registrar, its constructor will be called, giving you initialization on definition.

3: Template Class, Inheriting Base.

template<class T>
class Factory : public Base
{
public:
    Factory( int supply )
    {
        Factory<T>::s_Supply = supply;
        Q_UNUSED(s_Registrar)
    }
    int supply() const override
    {
        return s_Supply;
    }
private:
    static inline Registrar s_Registrar { new T() };
    static inline int s_Supply{0};
};

Notes: It is important for this to be a template, to give an inherently unique signature. This unique signature will ensure that a unique static Registrar will be created, hence running its constructor. Q_UNUSED(s_Registrar) is needed for RAII, ensuring the constructor will run. Thankfully this bit of ugly code can be hidden in this class which will be for the most part ignored here on out.

4: Your Derived Classes

class A : public Factory<A>
{
public:
    A() : Factory<A>(1337) {}
};
class B : public Factory<B>
{
public:
    B() : Factory<B>(0) {}
};

Notes: As long as you remember to public Factory<A> and : Factory<A>(1234), this derived class will register itself to QList<Base*>, and when de-referenced, will call the functions defined here.

And that's that. Running my program,

Debug /home/anon/Programming/Wiki/main.cpp:15
int main(int, char**)

int s_Registered.at(i)->supply() int :: 1337

Debug /home/anon/Programming/Wiki/main.cpp:15 int main(int, char**)

int s_Registered.at(i)->supply() int :: 0

Anon
  • 3,639