I find this question very interesting after reading the part of "Effective C++" about public inheritance. Before it would be common sense for me to say yes, because every square is a rectangle, but not necessarily other way around. However consider this code:
void makeBigger(Rectangle& r) {
r.setWidth(r.width() + 10);
}
This code is perfectly fine for a Rectangle
, but would break the Square
object if we passed it to makeBigger
- its sides would become unequal.
So how can I deal with this? The book didn't provide an answer (yet?), but I'm thinking of a couple of ways of fixing this:
Override setWidth()
and setHeight()
methods in Square
class to also adjust the other side.
Drawback: code duplication, unnecessary 2 members of Square
.
For Square
not to inherit from Rectangle
and be on its own - have size
, setSize()
etc.
Drawback: weird - squares are rectangles after all - it would be nice to reuse Rectangle
's features such as right angles etc.
Make Rectangle
abstract (by giving it a pure virtual destructor and defining it) and have a third class that represents rectangles that are not squares and inherits from Rectangle
. That will force us to change the above function's signature to this:
void makeBigger(NotSquare& r);
Can't see any drawbacks except having an extra class.
Is there a better way? I'm leaning towards the third option.
This is one of the key principles in OO design that I find gets handled incorrectly. Mr Meyer does an excellent job of of discussing it the book you are referring to.
The trick is to remember that the principles must be applied to concrete use cases. When using inheritence, remember that the key is that the "is a" relationship applies to an object when you want to use that object as a ... So whether a square is a rectangle or not depends on what you are going to be doing with rectangles in the future.
If you will be setting width and height of a rectangle independently, then no, a square is not a rectangle (in the context of your software) although it is mathematically. Thus you have to consider what you will be doing with your base objects.
In the concrete example you mention, there is a canonical answer. If you make makeBigger a virtual member function of rectangle, then each one can be scaled in a way that is appropriate to a class. But this is only good OO design if all the (public) methods which apply to a rectangle will apply to a square.
So let's see how this applies to your efforts so far:
I see this kind of thing in production code pretty often. It's excusable as a kludge to fix a gap in an otherwise good design, but it is not desirable. But it's a problem because it leads to code which is syntactically correct, but semantically incorrect. It will compile, and do something, but the meaning is incorrect. Lets say you are iterating over a vector of rectangles, and you scale the width by 2, and the height by 3. This is semantically meaningless for a square. Thus it violates the precept "prefer compile time errors to runtime errors".
Here you are thinking of using inheritance in order to re-use code. There's a saying "use inheritance to be re-used, not to re-use". What this means is, you want to use inheritance to make sure the oo code can be re-used elsewhere, as its base object, without any manual rtti. Remember that there other mechanisms for code re-use: in C++ these include functional programming and composition.
If square's and rectangles have shared code (e.g. computing the area based on the fact that they have right angles), you can do this by composition (each contains a common class). In this trivial example you are probably better off with a function though, for example: compute_area_for_rectangle(Shape* s){return s.GetHeight() * s.GetWidth());} provided at a namespace level.
So if both Square and Rectangle inherit from a base class Shape, Shape having the following public methods: draw(), scale(), getArea() ..., all of these would be semantically meaningful for whatever shape, and common formulas could be shared via namespace level functions.
I think if you meditate on this point a little, you'll find a number of flaws with your third suggestion.
Regarding the oo design perspective: as icbytes mentioned, if you're going to have a third class, it makes more sense that this class be a common base that meaningfully expresses the common uses. Shape is ok. If the main purpose is to draw the objects than Drawable might be another good idea.
There are a couple other flaws in the way you expressed the idea, which may indicate a misunderstanding on your part of virtual destructors, and what it means to be abstract. Whenever you make a method of a class virtual so that another class may override it, you should declare the destructor virtual as well (S.M. does discuss this in Effective C++, so I guess you would find this out on your own). This does not make it abstract. It becomes abstract when you declare at least one of the methods purely virtual -- i.e. having no implementation
virtual void foo() = 0; // for example
This means that the class in question cannot be instantiated. Obviously since it has at least one virtual method, it should also have the destructor declared virtual.
I hope that helps. Keep in mind that inheritence is only one method by which code can be re-used. Good design comes out of the optimal combination of all methods.
For further reading I highly recommend Sutter and Alexandrescu's "C++ Coding Standards", especially the section on Class Design and Inheritence. Items 34 "Prefer composition to inheritence" and 37 "Public inheritence is substitutability. Inherit, not to reuse, but to be reused.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With