Deriving an abstract class from concrete class
Asked Answered
H

7

14

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.
Hastate answered 21/11, 2008 at 22:20 Comment(3)
Can you edit your question to show some code samples please?Walloper
@Marcin: "No fruits have been harmed in the writing of this question" That made my day :)Savonarola
Added code snippets by request.Hastate
H
8

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).

Handspring answered 21/11, 2008 at 23:1 Comment(2)
Ok, this is very old now, but in the copy of Meyers I found "Item 33: Use inlining judiciously". I'm not sure if your copy has them labelled differently, or I'm missing the connection?Undenominational
That's 'Effective C++' as opposed to 'More Effective C++'Handspring
A
5

It would seem to me like an indication of a bad design. Could be forced if you wanted to take a concrete definition from a closed library and extend it and branch a bunch of stuff off it, but at that point I'd be seriously considering the guideline regarding Encapsulation over Inheritance.. If you possibly can encapsulate, you probably should.

Yeah, the more I think about it, this is a Very Bad Idea.

Abbe answered 21/11, 2008 at 22:23 Comment(0)
G
3

If you use the recommended practice of having inheritance model "is-a" then this pattern would pretty much never come up.

Once you have a concrete class, you are saying that it is something that you can actually create an instance of. If you then derive an abstract class from it, then something that is an attribute of the base class is not true of the derived class, which should set of klaxons that something's not right.

Looking at your example, a peach is not an apple, so it should not be derived from it. Same is true for Tomato deriving from Berry.

This is where I would usually advise containment, but that doesn't even seem to be a good model, since an Apple does not contain a Peach.

In this case, I would factor out the common interface -- PieFilling or DessertItem.

Gagnon answered 21/11, 2008 at 22:53 Comment(4)
"attribute of the base class" I am not sure what you call an "attribute of the base class".Carrera
"attribute" was probably a poor word choice, since it also refers to a programming concept.Gagnon
What I meant was "trait" or "something that is true for." i.e. " It is true that you can instantiate the base class, but it is not true that you can instantiate the derived class, therefore derived it is not the case that derived "is-a" base.Gagnon
You never, ever, have derived "is-a" base.Carrera
S
2

Not necessarily wrong, but definitely smelly. Especially if you leave the fruit out in the sun for too long. (And I don't think my dentist would like me eating concrete apples.)

Though, the main thing I see here that's smelly isn't so much the abstract class derived from a concrete class, but the REALLY DEEP inheritance hierarchy.

EDIT: re-reading I see that these are two hierarchies. All the fruit stuff got me mixed up.

Socioeconomic answered 21/11, 2008 at 22:26 Comment(0)
S
1

a bit unusual, but if you had some other subclass of the base class and the subclasses of the abstract class had enough common stuff to justify the existance of the abstract class like:

class Concrete
{
public:
    virtual void eat() {}
};
class Sub::public Concrete { // some concrete subclass
    virtual void eat() {}
};
class Abstract:public Concrete // abstract subclass
{
public:
    virtual void eat()=0;
    // and some stuff common to Sub1 and Sub2
};
class Sub1:public Abstract {
    void eat() {}
};
class Sub2:public Abstract {
    void eat() {}
};
int main() {
    Concrete *sub1=new Sub1(),*sub2=new Sub2();
    sub1->eat();
    sub2->eat();
    return 0;
}
Sugihara answered 21/11, 2008 at 23:18 Comment(0)
K
0

Hmmm...by thinking "what a....." for a couple of seconds, I come to a conclusion it is not common... Also, I would not derive Peach from Apple and Tomato from Berry...do you have any better example?:)

It's a lot of weird shit you can do in C++...I can't even think of 1% of it...

About override a virtual with a pure virtual, you can probably just hide it and it will be really weird...

If you can find a stupid C++ compiler that would link this function as a virtual, then you will get runtime pure virtual function call...

I think this can only be done for a hack and I have no idea what kind of hack really...

Kaliningrad answered 21/11, 2008 at 22:31 Comment(1)
"About override a virtual with a pure virtual, you can probably just hide it" why would a function with the same signature not override a base class virtual function?Carrera
W
0

To answer your first question, you can do this since users of Apple, if given a concrete instance derived from Peach will not know any different. And the instance will not know its not an Apple (unless there are some virtual functions from Apple that are overridden that you didn't tell us about).

I can't yet imagine how useful it would be to override a virtual function with a pure virtual one - is that even legal?

In general you want to conform with Scott Meyers "Make all non-leaf classes abstract" item from his books.

Anyway, apart from that what you describe seems to be legal - its just that I can't see you needing it that often.

Walloper answered 21/11, 2008 at 22:33 Comment(1)
"I can't yet imagine how useful it would be to override a virtual function with a pure virtual one - is that even legal?" Yes, why wouldn't it be?Carrera

© 2022 - 2024 — McMap. All rights reserved.