How to make it so parent classes don't repeat a grandfather method that was already executed

Viewed 54

We have a child class practicante who inherits from 2 classes: estudiante and empleado and both inherits from a grandfather-class persona. All of them have the method que_eres() which writes what class is the object (just for an example):

#include <iostream>
using namespace std;

class persona
{
    public:
    void que_eres() { cout<<"Soy una persona."<<endl; }
};

class estudiante: public persona
{
    public:
    void que_eres()
    {
        cout<<"Soy un estudiante."<<endl;
        persona::que_eres();
    }
};

class empleado: public persona
{
    public:
    void que_eres()
    {
        cout<<"Soy un empleado."<<endl;
        persona::que_eres();
    }
};

class practicante: public estudiante, public empleado
{
    public:
    void que_eres()
    {
        estudiante::que_eres();
        empleado::que_eres();
    }
};

int main()
{
    practicante jose;
    jose.que_eres();
}

The result is:

Soy un estudiante.
Soy una persona.
Soy un empleado.
Soy una persona.

It's not bad, but it would be better if Soy una persona were only written once (that is, that the method que_eres() of the grandfather would be executed just once). Is that possible?

For example, in Python we can do this:

class persona:
    def que_eres(self): 
        print("Soy una persona.")

class estudiante(persona):
    def que_eres(self): 
        print("Soy un estudiante.")
        super().que_eres()

class empleado(persona):
    def que_eres(self): 
        print("Soy un empleado.")
        super().que_eres()

class practicante(estudiante,empleado):
    def que_eres(self): 
        super().que_eres()

jose = practicante()
jose.que_eres()

And the result is just:

Soy un estudiante.
Soy un empleado.
Soy una persona.
1 Answers

This is because you have two persona instances in any practicante: one for estudiante and one for empleado and nowhere did you say that it’s always the same persona. (Online demo).

If you want all these these persona to be the same. This is known as the diamond problem. You need to use public virtual inheritance for every intermediary class that inherits the persona.

But there is more to it: since there will be only one virtual base, you’ll need to take care. This means that practicante needs to provide for the persona constructor, since otherwhise the compiler wouldn’t know which of estudiante and empleado invokation of the constructor would have priority. Of course, if you have only the default consturctor this will work almost as is (online demo):

class estudiante: public virtual persona
{
    public:
        ...
};
class empleado: public virtual persona
{
    public:
        ...
};

And similarly, you’ll have to make sure that there is no double invocation by avoiding the mechanics upstream calls that you use, and having the different subclasses cooperate for printing who they but are only once. An easy trick could be to pass a string parameter (default ""s) to que_eres() to build the string to be printed in the calls, and let only the base class printing it. If you don’t want to change the signature of que_eres() you can of course use an auxiliary function.

Here for example an implementation with only one que_eres() in the base class, that uses a virtual quien_soy() that every class overrides to build the presentation string. Virtual bases must then ensure that they appear only once in the string. Online demo:

class persona
{
    protected:
        virtual string quien_soy(string x=""s) {
            const auto s="Soy una persona.\n"s; 
            auto p=x.find(s);
            if (p==string::npos)
                x += s;
            else {
                x = x.substr(0,p)+ x.substr(p+s.size()) +s;
            }
            return x;
        }
    public:
        void que_eres() { cout<<quien_soy() <<endl; }
        virtual ~persona() {}
};

class estudiante: public virtual persona
{
    protected:
        string quien_soy(string x=""s) override
        {
            return persona::quien_soy(x+"Soy un estudiante.\n");
        }
};

class empleado: public virtual persona
{
    protected:
        string quien_soy(string x=""s) override
        {
            return persona::quien_soy(x+"Soy un empleado.\n");
        }
};

class practicante: public estudiante, public empleado
{
    protected:
        string quien_soy(string x=""s) override
        {
            return empleado::quien_soy(estudiante::quien_soy(x+"Soy un praticante, por consiguiente:\n"));
        }
};

All this make multiple inheritance less trivial than it appears at first sight, at least in a diamond case.

Hint: in the real life a persona is a persona. An estudiante and an empleado are not a person but roles that are taken by a person. Composition over inheritance would make this more realistic, leaving the personas the possibility to change role over their life ;-)

Related