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()
andsetHeight()
methods inSquare
class to also adjust the other side.Drawback: code duplication, unnecessary 2 members of
Square
.For
Square
not to inherit fromRectangle
and be on its own - havesize
,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 fromRectangle
. 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.
You say: "because every square is a rectangle" and here the problem lies exactly. Paraphrase of famous Bob Martin's quote:
(original explanation here: http://blog.bignerdranch.com/1674-what-is-the-liskov-substitution-principle/)
So surely every square is a rectangle, but this doesn't mean that a class/object representing a square "is a" class/object representing a rectangle.
The most common real-world, less abstract and intuitive example is: if two lawyers struggle in the court representing a husband and a wife in the context of a divorce, then despite the lawyers are representing the people during a divorce and being currently married they are not married themselves and are not during a divorce.
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.
It turns out the easier solution is
Works perfectly well on squares, and correctly returns a rectangle even in that case.
[edit] The comments point out that the real problem is the underlying call to
setWidth
. That can be fixed in the same way:Again, changing the width of a square gives you a rectangle. And as the
const
shows, it gives you a newRectangle
without changing the existing Rectangle The previous function now becomes even easier:Except from having an extra class there are no serious drawbacks of your 3rd solution (called also Factor out modifiers). The only I can think of are:
Suppose I have a derived Rectangle class with one edge being a half of the other, called for example HalfSquare. Then according to your 3rd solution I'd have to define one more class, called NotHalfSaquare.
If you have to introduce on more class then let it be rather Shape class both Rectangle, Square and HalfSquare derive from
If you want your
Square
to be aRectangle
, it should publicly inherit from it. However, this implies that any public methods that work with aRectangle
must be appropriately specialised for aSquare
. In this contextshould not be a standalone function but a virtual member of
Rectangle
which inSquare
is overridden (by providing its own) or hidden (byusing makeBigger
in theprivate
section).Regarding the issue that some things you can to do a
Rectangle
cannot be done to aSquare
. This is a general design dilemma and C++ is not about design. If somebody has a reference (or pointer) to aRectangle
that actually is aSquare
and want to do an operation that makes no sense for aSquare
, then you must deal with that. There are several options:1 use public inheritance and make
Square
throw an exception if an operation is attempted that is not possible for aSquare
This really is awkward and not appropriate if
change_width()
orchange_height()
are integral parts of the interface. In such a case, consider the following.2 you can have one
class Rectangle
(which may happen to be square) and, optionally, a separateclass Square
that can be converted (static_cast<Rectangle>(square)
) to aRectangle
and hence act as a Rectangle, but not be modified like aRectangle
This option is the correct choice if you allow for changes to the
Rectangle
that can turn it into aSquare
. In other words, if yourSquare
is not aRectangle
, as implemented in your code (with independently modifiable width and height). However, sinceSquare
can be statically cast to aRectangle
, any function taking anRectangle
argument can also be called with aSquare
.My idea: You have a superclass, called Shape. Square inherits from Shape. It has the method resize(int size ). A Rectangle is ClassRectangle, inheriting from Shape but implementing interface IRecangle. IRectangle has method resize_rect(int sizex, int size y ).
In C++ interfaces are created by the usage of so called pure virtual methods. It is not fully well implemented like in c# but for me this is even better solution than third option. Any opinions ?