c++ – How to implement a mandatory call to a base class method?

Question:

I ran into an interesting problem when writing a program in C ++, there is a parent class, there is its heir, in the heir I override some function, but I want to call the base class function, the solution is obvious and simple

class A
{
public:
    virtual void Func()
    {
        //do something
    }
}

class B : A
{
public:
    void Func() override
    {
        A::Func();
        //aditional logic
    }
}

Everything works and everything is cool, only duplication of code confuses me, I have to call the base class method in all heirs, and what is worse, theoretically, I can forget this call and there will be strange errors. The decision also came quickly, I decided to do it like this

class A
{
public:
    virtual void Func() final
    {
        //do something
        FuncInternal();
    }
protected:
    virtual void FuncInternal() = 0;
}

class B : A
{
    void FuncInternal() override
    {
        //aditional logic
    }
}

Now the base class method will always work first, then the successor, but I realized that if the inheritance level is more than 2, then I will need to enter FuncInternal1, FuncInternal2, etc. And here I am stuck, how can I solve the problem of getting rid of the duplication of calling the base function in all descendants in such a way that the implementation does not differ depending on the number of descendants.

I think that I'm missing something from the general knowledge of OOP, binding to the language is not required. Thank you

UPDATE1

The source of the problem is the idea to make an abstract interface class, for example, IRenderable, and if there is an object that inherits from such a class Monster : IRenderable, then I am sure that Monster will have a render function and that when this function is called, a certain single set of actions will be performed described in IRenderable.

Answer:

Since you write that you don't care about a particular language, here's an example with honest reflection in C#.

Idea: it is not necessary to call the "parent" implementation, instead each of the generated classes can, if they want, add their own implementation of the method. The base method calls all implementations in turn.

The classes look like this:

class C1
{
    public void F(int x)
    {
        // вызываем в цикле все имплементации
        foreach (var impl in Util.GetAllImplementations<Action<int>>(this, "FImpl"))
            impl(x);
    }

    // имплементация в C1
    private void FImpl(int x) => Console.WriteLine($"From C1::F({x})");
}

class C2 : C1
{
    // тут нету имплементации
}

class C3 : C2
{
    // имплементация в C1
    private void FImpl(int x) => Console.WriteLine($"From C3::F({x})");
}

Utility class (reflection horrors):

static class Util
{
    static public IEnumerable<DT> GetAllImplementations<DT>(object obj, string name)
    {
        Type[] argTypes = GetArgTypes(typeof(DT));
        for (Type curr = obj.GetType(); curr != null; curr = curr.BaseType)
        {
            var method = curr.GetMethod(
                    name, BindingFlags.NonPublic | BindingFlags.Instance,
                    null, argTypes, null);
            if (method == null)
                continue;
            yield return (DT)(object)Delegate.CreateDelegate(typeof(DT), obj, method);
        }
    }

    static Type[] GetArgTypes(Type delegateType)
    {
        if (delegateType == typeof(Action))
            return new Type[0];
        if (!delegateType.IsGenericType)
            throw new ArgumentException("Expected delegate type");
        var typedef = delegateType.GetGenericTypeDefinition();
        var isFunc = (typedef.Name.StartsWith("Func`"));
        var isAction = (typedef.Name.StartsWith("Action`"));
        if ((!isFunc && !isAction) || typedef.Namespace != "System")
            throw new ArgumentException("Expected delegate type");
        var argTypes = delegateType.GetGenericArguments();
        if (isFunc)
            Array.Resize(ref argTypes, argTypes.Length - 1);
        return argTypes;
    }
}

We run, we get the result:

class Program
{
    static void Main(string[] args)
    {
        new C3().F(1);
    }
}

From C3::F(1)
FromC1::F(1)


I am sure that in C ++ you can get the same result using some kind of template magic.

Scroll to Top