Home > Back-end >  Muiltple inheritance: first base class dependent on function in the second base class
Muiltple inheritance: first base class dependent on function in the second base class

Time:08-04

I have a lot of different child classes (15 ) that all have the same function centerAt defined inside of them. Those child classes each inherits a different classes in an extern library: what is mutual for all of them is that they have the function setPos that centerAt is dependent on.

My goal is to avoid code repetition and I therefore came up with a solution moving centerAt to a base class that all the child class could inherit along side with the base classes from the external library: multiple inheritance.

I came up with this code

class InternalBase {
public:
    void centerAt(int x, int y) {
        // Do some math
        setPos(newx, newy)
    }
    virtual void setPos() = 0; // Should be replaced by the setPos in ExternalBase
};

// There are 15  different external base classes used, here only 2 are shown
class ExternalBase1 { // this is from an external library
public:
    void setPos() { /* internal definition */} 
};

class ExternalBase2 { // this is from an external library
public:
    void setPos() { /* internal definition */} 
};
class Child1 : public InternalBase, public ExternalBase1 {
public:
    // A lot of different functions
};

class Child2 : public InternalBase, public ExternalBase2 {
public:
    // A lot of different functions
};

int main() {
    Child1 myobj;
    myobj.centerAt(120, 394);
    return 0;
}

Unfortunately, the object can't be constructed: the compiler complaining that is an abstract class - probably because setPos weren't inherited from ExternalBase1. How can I fix this, so Child1 inherits setPos from ExternalBase1 and InternalBasethereby can use the setPos defined in ExternalBase1.

This issue is identical to Override pure virtual method with parallel base class method?, but the big knot is, that because I am dealing with an external library, it is not possible to use the given solution (making ExternalBase1 inherit InternalBase first: class ExternalBase1: public virtual InternalBase).

I would like to avoid the use of templates.

CodePudding user response:

Using templates is the simplest solution. Templates are one of the distinct features of C and there's no reason not to use them.

template<typename T>
class ExternalBaseMixin : public InternalBase, public T {

public:
    using T::T;

    void setPos(int x, int y) override
    {
        T::setPos(x, y);
    }
};

That's it. Then, it's simply a matter of:

class Child1 : public ExternalBaseMixin<ExternalBase1> {

// ...

class Child2 : public ExternalBaseMixin<ExternalBase2> {

// ...

etc...

There is no comparable built-in, non-template C feature that accomplishes the same thing. Any non-template solution will end up doing the exact same thing that this does, but with much more typing, more code, and with a more error-prone result. This is fairly bulletproof, as long as a single requirement gets met: always inherit from ExternalBaseMixin. As long as that happens everything gets overriden and combined together, in the proper order. Plus, this also guarantees inheritance order: that InternalBase gets constructed before the external class, if that matters.

CodePudding user response:

You can use CRTP and make InternalBase a template type .

This introduces very little changes to your code.

template <class Derived>
class InternalBase {
public:
    void centerAt(int x, int y) {
        // Do some math
        Derived& derived = static_cast<Derived&>(*this);
        derived.setPos(x, y);
    }
};

class ExternalBase1 { 
public:
    void setPos(int x, int y) {} 
};

class Child1 : public InternalBase<Child1>, public ExternalBase1 {
};

int main() {
    Child1 myobj;
    myobj.centerAt(120, 394);
    return 0;
}

See it work here: https://godbolt.org/z/Tsej4Gcfd

CodePudding user response:

A quick fix for your problem is just explicitly defining on each child the setPos() function to use. (I adjusted some ill defined code that I guess comes from this being an example, but setPos and the virtual definitions provided did not match in signature):

#include <iostream>

class InternalBase {
public:
    void centerAt(int x, int y) {
        // Do some math
        setPos(x, y);
    }
    virtual void setPos(int x, int y) = 0;
};

class ExternalBase1 {
public:
    void setPos(int x, int y) { std::cout << "Inside ExternalBase1::setPos" << std::endl; } 
};

class Child1 : public InternalBase, public ExternalBase1 {
public:
    void setPos(int x, int y) { ExternalBase1::setPos(x, y); } 
    // A lot of different functions
};

int main() {
    Child1 myobj;
    myobj.centerAt(120, 394);
    return 0;
}

Although I would suggest, if you have the time, to try to solve the problem structuring the code from a different approach (templates are ideal here, as already someone answered).

  • Related