0

I'm working on a large software, that heavily relies on the call super anti-pattern.

Basically, an interface defines an init() virtual method that is then overridden in every classes that inherits it. When init() is called on the class, EVERY overloaded init methods are cascaded, from the base class to the top daughter class, and all in between.

But the user HAS to call its direct parent's init() method in its own, which results in code like this:

class myClass : public Base
{
    virtual void init()
    {
        Parent::Init() // often forgotten, or misplaced by the developer
        // Do some init stuff
    }
}

An alternative to this is usually to use delegates. The Base class has stuff to do in init, that is declared final in Base, and delegates part of it to an onInit() method that inheriting classes should override. But this does not cascade the calls to all parent classes as I'd like it to.

The alternative I've first implemented is a variant of the delegate approach. Both daughter classes and Base implement a onInit() and a init() method. init() calls Parent::init() followed by a call to onInit() on itself, and is auto-generated using template meta-programming & macros. and onInit() contains the class-specific code.

# define DELEGATE(T, Parent) \
     void init() override { Parent::init(); T::onInit(); }

struct Base
{
    virtual void init() { Base::onInit(); }
    virtual void onInit() {}
};

struct A : public Base
{
     DELEGATE(A, Base)
     void onInit() override { /* MyCustom code for A */ }
};

struct B : public A
{
     DELEGATE(B, A)
     void onInit() override { /* MyCustom code for B */ }
};

This works pretty well.. except that multiple inheritance becomes a mess, and even if handled, diamond inheritance causes issues with duplicated calls.

Which leaves me wondering: I can't be the only person looking for a design pattern solving my issue, and the stackoverflow community must know about it :)

Have you ever encountered a design pattern for this? I'm really looking forward to your ideas!

Jarod42
  • 203,559
  • 14
  • 181
  • 302
lagarkane
  • 915
  • 2
  • 9
  • 22
  • I don't get it. Doesn't this actually call the derived class's `onInit` twice? Once from `Base`'s `init`, and once from the expansion of the `DELEGATE` macro. – Brian Bi May 22 '19 at 14:51
  • Sorry, you're right. Base::init() should call Base::onInit() insteead on onInit() I fixed my post, sorry for that – lagarkane May 22 '19 at 15:00
  • You might use CRTP, instead of MACRO (and so handle easily multiple inheritance). – Jarod42 May 22 '19 at 15:31
  • For duplicated calls, you might pass a `std::set` to conditionally call `onInit`. – Jarod42 May 22 '19 at 15:36
  • Unfortunately, [proposal for `std::bases` and `std::direct_bases`](https://stackoverflow.com/questions/18435001/what-is-the-status-of-n2965-stdbases-and-stddirect-bases) has been rejected. – Jarod42 May 22 '19 at 15:40

1 Answers1

0

This is what I would to protect yourself from forgetting about overriding in children classes. This is simple template method design pattern implementation:

class Base {
public:
    void init() {
        //base init steps
        childInit();
    }

private:
    virtual void childInit() = 0;
};

class Child : public Base {
private:
    void childInit() override { /*init the child*/}
};
bartop
  • 9,971
  • 1
  • 23
  • 54
  • Thanks for your answer @bartop but this won't do for me, as I want all the childs, and the child's childs to get their init() method called in a cascade. If I had a class A inheriting Base, a class B inheriting A and a class C inheriting B for instance, I'd want a call to init() on C to perform the following calls: Base::init(); A::init(): B::init(); C::init() – lagarkane May 22 '19 at 15:08