Search code examples
c++inheritanceabstract-classpure-virtual

Deriving an abstract class from concrete class


Let's say we have a concrete class Apple. (Apple objects can be instantiated.) Now, someone comes and derives an abstract class Peach from Apple. It's abstract because it introduces a new pure virtual function. The user of Peach is now forced to derive from it and define this new function. Is this a common pattern? Is this correct to do?

Sample:


class Apple
{
public:
    virtual void MakePie();
    // more stuff here
};

class Peach : public Apple { public: virtual void MakeDeliciousDesserts() = 0; // more stuff here };

Now let's say we have a concrete class Berry. Someone derives an abstract class Tomato from Berry. It's abstract because it overwrites one of Berry's virtual functions, and makes it pure virtual. The user of Tomato has to re-implement the function previously defined in Berry. Is this a common pattern? Is this correct to do?

Sample:


class Berry
{
public:
    virtual void EatYummyPie();
    // more stuff here
};

class Tomato : public Berry { public: virtual void EatYummyPie() = 0; // more stuff here };

Note: The names are contrived and do not reflect any actual code (hopefully). No fruits have been harmed in the writing of this question.


Solution

  • Re Peach from Apple:

    • Don't do it if Apple is a value class (i.e. has copy ctor, non-identical instances can be equal, etc). See Meyers More Effective C++ Item 33 for why.
    • Don't do it if Apple has a public nonvirtual destructor, otherwise you invite undefined behaviour when your users delete an Apple through a pointer to Peach.
    • Otherwise, you're probably safe, because you haven't violated Liskov substitutability. A Peach IS-A Apple.
    • If you own the Apple code, prefer to factor out a common abstract base class (Fruit perhaps) and derive Apple and Peach from it.

    Re Tomato from Berry:

    • Same as above, plus:
    • Avoid, because it's unusual
    • If you must, document what derived classes of Tomato must do in order not to violate Liskov substitutability. The function you are overriding in Berry - let's call it Juice() - imposes certain requirements and makes certain promises. Derived classes' implementations of Juice() must require no more and promise no less. Then a DerivedTomato IS-A Berry and code which only knows about Berry is safe.

    Possibly, you will meet the last requirement by documenting that DerivedTomatoes must call Berry::Juice(). If so, consider using Template Method instead:

    class Tomato : public Berry
    {
    public:
        void Juice() 
        {
            PrepareJuice();
            Berry::Juice();
        }
        virtual void PrepareJuice() = 0;
    };
    

    Now there is an excellent chance that a Tomato IS-A Berry, contrary to botanical expectations. (The exception is if derived classes' implementations of PrepareJuice impose extra preconditions beyond those imposed by Berry::Juice).