Search code examples
c++inheritancepolymorphismpure-virtual

Specialization of pure virtual base method in derived class


I wondered if there is a common way/pattern to let a derived class have a somewhat more specialized version of a pure virtual method in the given base class.

class Base {
public:
    Base() = default;
    virtual ~Base() = 0;
    virtual void foo(int bar) = 0;
};
inline Base::~Base() {}

class Derived public Base {
public:
    Derived() = default;
    ~Derived() = default;
    void foo(int bar) override {/*...*/}
};

class SpecializedDerived : public Base {
public:
    SpecializedDerived() = default;
    ~SpecializedDerived() = default;
    void foo(int bar, double additionalParameter) override {/*...*/}
};

The override in the SpecializedDerived class is impossible, because the signature of the method does not correspond to the one in the pure virtual Base class.

Now, is there a way to achieve the described design? Is there a way to implement "more specialized methods" as there is class inheritance which will allow you to implement "more specialized classes"?

While typing I reckoned that my wish is more a kind of "Dude, I just want you to provide an iterate(.) function of some sort!" thing.

The only idea that came to my mind so far is something like

class Base {
public:
    Base() = default;
    virtual ~Base() = 0;
    virtual void foo(int bar) = 0;
};
inline Base::~Base() {}

class SpecializedDerived : public Base {
public:
    SpecializedDerived(double addParam) : additionalParam_(addParam) {}
    ~SpecializedDerived() = default;
    void foo(int bar) override {
        iterate(bar, additionalParam_);
        return;
    }
private:
    double additionalParam_;
    void foo(int bar, double additionalParam) {/*...*/}
};

Where this internal function call is actually redundant as you could just do:

class SpecializedDerived : public Base {
public:
    SpecializedDerived(double addParam) : additionalParam_(addParam) {}
    ~SpecializedDerived() = default;
    void foo(int bar) override {/* code using additionalPara_ */}
private:
    double additionalParam_;
};

Solution

  • Why is a matchnig signature necessary ?

    The idea behind polymorphism and virtual functions, is that the caller doesn't have to know any details about the real class of the object it uses:

    Example:

    Base *my_object = find_dynamically_a_relevant_object (...);              
    my_object->foo(10);   // could be a Derived or a SpecializedDerived
    
    std::vector<Base*> container;  
    ...                   // somehow you populate the container with a mix of 
    ...                   // Derived AND SpecializedDerived objects 
    for (auto x: container)
        x->foo(std::srand());  
    

    This is why the signature must match exactly the one defined in the base class.

    But can a different signature also be used ?

    Now you could very well define an overloaded foo() with a completely different signature under three conditions:

    • It would not be an override: it's a different function with the same name. * You could invoke the overloaded foo() with its additional parameters only if you're sure that the object has the right type.
    • You would have to make sure that an override is provided with the matching signature is provided (because it is a pure virtual). This could for example just invoke your overload, using some arbitrary values for the extra parameters)

    Example:

    class SpecializedDerived : public Base {
    public:
        SpecializedDerived() = default;
        ~SpecializedDerived() = default;
        void foo(int bar) override { foo(bar, 0.0); }
        void foo(int bar, double additionalParameter)  {cout<<"specialized "<<bar<<" "<<additionalParameter<<endl;}
    };
    
    ... // elsewhere, for example in main(): 
    
    SpecializedDerived my_obj;  
    my_obj.foo(10);  // use the override of the base
    my_obj.foo(10, 37.2); // use the overload
    
    // suppose that p is a Base* like in the first example
    auto *q = dynamic_cast<SpecializedDerived*>(p); 
    if (q)  // dynamic cast makes this nullptr if not the right type
        q->foo(10, 37.2); 
    else cout << "Not specialized"<<endl; 
    

    Override with a behavior depending on some adiditional data

    Now if you want to use your foo() in strictly polymorphic context but still have some (hidden) additional parameters, there are several possibilities, such as for example:

    • you could extend the base signature and add an additional, mostly unused parameter with a default value. This is a bad idea in most cases: what if a new derived class comes with yet another parameter.
    • you could inject the additional parameter before doing the call (either at construction as you have yourself suggested, or using a setter to change the value whenever you need). Works most of the cases. The only risk is to make sure that the additional parameter was set correctly before calling foo()
    • you could change the signature to use as single parameter an object that holds all the real parameters. This requires some extra overhead, but is extremely flexible with variable parameters. The only thing is that the type of this object may very well need to be polymorphic as well, in which case you must be sure that the right parameter type is used for the right object. Tis appears to me super-powerful but super-super-risky.