Rectangle: struct or class?

S

Steven T. Hatton

The following may strike many of you as just plain silly, but it represents
the kind of delelima I find myself in when trying to make a design
decision. This really is a toy project written for the purpose of learning
to work with C++. It therefore makes some sense for me to give the
situation the amount of consideration presented below. To be quite honest,
I'm amazed at the amount there is to say about such a seemingly simple
situation.

I'm trying to learn to use C++ the way Stroustrup tells us to in TC++(PL).
He has a few different guidelines for determining if something should be
treated as a class or a struct (in the 'C' sense). One measure is whether
it has an invariant that needs to be preserved. So, say I have a rectangle
{x,y,w,h} where x and y are the coordinates of the top left corner, and w
and h are width and height respectively. I can arbitrarily change any
element of that set and still have a rectangle (assuming w > 0, h > 0, or I
assign meaning to the alternatives). Another criteria he has is whether
there are two possible representations of the same abstraction. If so, it
argues that it should be a class To that I say yes.

struct Rectangle{size_t x, y, w, h;};
struct Point{size_t x,y;};
struct RectanglePts{Point xy, dxdy;}

I may also want some convenience functions such as

Rectangle& Rectangle::widen(const size_t dw){ this->w += dw; return this; }
Rectangle& Rectangle::move(const Point dxy){
this->x += dxy.x;
this->y += dxy.y;
return this; }

Interestingly, there is an operation for which when an invariant can be
identified. The displacement between Rectangle::xy and Rectangle::dxdy
should be preserved under translations. Likewise scale operations should
preserve the ratio between the lengths of the sides.

Then I ask if Rectangle should derive from Point. There are arguments for
either choice. He suggests asking the question "can the object have two of
the things you are considering as a base class". At first I might say
"yes", I can have more than two points. I only need two points to define
the rectangle. That argues that the Rectangle should have Point members,
rather than be a Point. But what if we say a point isn't just a point,
it's a location. So we ask again, "can it have two?". Well, yes and no.
It can have many locations within it. Mathematically, there are an
infinite number of points in a rectangle. But the rectangle as a whole
only realistically has one position.

And it makes some sense to treat the rectangle as a position with a size and
shape. Rather than thinking of a Point as a pair of coordinates, or a
location, we might think of it as a geometric object. It could be
considered as the fundamental geometric object. We will never draw a
geometric object on a screen without it having a location. I can use the
same set of operations to move any geometric object, as I use to move a
point. I might even write some operators to add two points as vectors, and
then inherit these in other geometric objects. If I add a Point to a
rectangle it is equivalent to moving the rectangel such that the
coordinates of the top left corner have the components of the point added
to them.

I have not completely definitive test to determine whether a rectangle
should be a class or a struct. Nor can I clearly decide if it should be a
Point and have another Point, or if it should have two points. I favor the
former for the reasons involving positioning.

But now, I have to consider a point. Can it have two representations? A
stupid {x,y} odered pair? Yup! {r cos(theta), r sin(theta)}. I even
briefly entertained the idea of inheriting from std::complex to get my
Point class.

But is it a class? It doesn't seem to have any other invariant than "it has
two components". But that /is/ an invariant that is determined when the
object is created. But that is true of any struct with two members.

So what's the advantage of protecting the components of Point? One argument
people give is that it prevents me from inadvertently modifying the data.
That seems a bit spurious to me in this case. Another, more reasonable
argument that Stroustrup doesn't mention is the notion of event
notification. If I change a value on Point, I may want to notify the
graphics rendering software of the change so it can schedule an update. If
I call a method to make the change, I can have the event fired as a side
effect.

So I make the components protected. So what? What dose that cost me? The
Point that I inherit in Rectangle acts just the way I want it. It inherits
everything from point that I need it to. OTOH, when I try to modify the
components of the lower right corner dxdy, I find I am not permitted to
access the protected data. So, either I use the public interface to modify
the data, or make Rectangle or some standalone function a friend of Point.
The former is a cyclic reference, and bad design, as a general rule. The
latter is something I find to be in bad taste.

If my public interface to point is well designed, writing that bit of extra
code within Rectangle is no big deal. There is one last factor I would
consider, and I honestly don't know what the answer is. Does a decent
compiler (g++, in my case) optimize away the function calls such as:

Point<T>& operator+=(const Point& p)
{
this->x += p.x;
this->y += p.y;
return *this;
}
?
 
B

Billy N. Patton

After 26+ years of dealing with rectangles and closed polygons. you most
surely want it to be a class. One suggestion. While a rectangle with a
point, width and height work well for drawing at some point it will have
to be converted to a polygon so that ll figures may be treated the same.
I would suggest that you deal with rectangles as 2 points, any 2
opposite corners. Pick 2 and do everything that way. Here, it's always
been the lower left and upper right. Id drawinf picture to the screen
are m=you main objective then by all means keep only a point,w and h.

As per class vs struct. Struct will by you nothing is your writing
methods of a class to deal with the rect.

class rect{
private:
int llx,lly,urx,ury;
public:
rect() { llx = lly = MAX_INT32; urx = ury = MAX_INT32; };
move
rotate
mirror
flip
copy
...
};


Over the past 15 years I have simulated c++ objects with c and structures.
typedef struct
{
int llx,lly,urx,ury;
} rect_t;
typedef rect_p *rect_t;

rect_move(rect_p,int x,int y)


Here I used a struct whan a class object would have same me the trouble
of knowing where a structure existed or has it been initailized.
I believe all this would have been much easire and easire to read if I
had started with c++ classes.

The following may strike many of you as just plain silly, but it represents
the kind of delelima I find myself in when trying to make a design
decision. This really is a toy project written for the purpose of learning
to work with C++. It therefore makes some sense for me to give the
situation the amount of consideration presented below. To be quite honest,
I'm amazed at the amount there is to say about such a seemingly simple
situation.

I'm trying to learn to use C++ the way Stroustrup tells us to in TC++(PL).
He has a few different guidelines for determining if something should be
treated as a class or a struct (in the 'C' sense). One measure is whether
it has an invariant that needs to be preserved. So, say I have a rectangle
{x,y,w,h} where x and y are the coordinates of the top left corner, and w
and h are width and height respectively. I can arbitrarily change any
element of that set and still have a rectangle (assuming w > 0, h > 0, or I
assign meaning to the alternatives). Another criteria he has is whether
there are two possible representations of the same abstraction. If so, it
argues that it should be a class To that I say yes.

struct Rectangle{size_t x, y, w, h;};
struct Point{size_t x,y;};
struct RectanglePts{Point xy, dxdy;}

I may also want some convenience functions such as

Rectangle& Rectangle::widen(const size_t dw){ this->w += dw; return this; }
Rectangle& Rectangle::move(const Point dxy){
this->x += dxy.x;
this->y += dxy.y;
return this; }

Interestingly, there is an operation for which when an invariant can be
identified. The displacement between Rectangle::xy and Rectangle::dxdy
should be preserved under translations. Likewise scale operations should
preserve the ratio between the lengths of the sides.

Then I ask if Rectangle should derive from Point. There are arguments for
either choice. He suggests asking the question "can the object have two of
the things you are considering as a base class". At first I might say
"yes", I can have more than two points. I only need two points to define
the rectangle. That argues that the Rectangle should have Point members,
rather than be a Point. But what if we say a point isn't just a point,
it's a location. So we ask again, "can it have two?". Well, yes and no.
It can have many locations within it. Mathematically, there are an
infinite number of points in a rectangle. But the rectangle as a whole
only realistically has one position.

And it makes some sense to treat the rectangle as a position with a size and
shape. Rather than thinking of a Point as a pair of coordinates, or a
location, we might think of it as a geometric object. It could be
considered as the fundamental geometric object. We will never draw a
geometric object on a screen without it having a location. I can use the
same set of operations to move any geometric object, as I use to move a
point. I might even write some operators to add two points as vectors, and
then inherit these in other geometric objects. If I add a Point to a
rectangle it is equivalent to moving the rectangel such that the
coordinates of the top left corner have the components of the point added
to them.

I have not completely definitive test to determine whether a rectangle
should be a class or a struct. Nor can I clearly decide if it should be a
Point and have another Point, or if it should have two points. I favor the
former for the reasons involving positioning.

But now, I have to consider a point. Can it have two representations? A
stupid {x,y} odered pair? Yup! {r cos(theta), r sin(theta)}. I even
briefly entertained the idea of inheriting from std::complex to get my
Point class.

But is it a class? It doesn't seem to have any other invariant than "it has
two components". But that /is/ an invariant that is determined when the
object is created. But that is true of any struct with two members.

So what's the advantage of protecting the components of Point? One argument
people give is that it prevents me from inadvertently modifying the data.
That seems a bit spurious to me in this case. Another, more reasonable
argument that Stroustrup doesn't mention is the notion of event
notification. If I change a value on Point, I may want to notify the
graphics rendering software of the change so it can schedule an update. If
I call a method to make the change, I can have the event fired as a side
effect.

So I make the components protected. So what? What dose that cost me? The
Point that I inherit in Rectangle acts just the way I want it. It inherits
everything from point that I need it to. OTOH, when I try to modify the
components of the lower right corner dxdy, I find I am not permitted to
access the protected data. So, either I use the public interface to modify
the data, or make Rectangle or some standalone function a friend of Point.
The former is a cyclic reference, and bad design, as a general rule. The
latter is something I find to be in bad taste.

If my public interface to point is well designed, writing that bit of extra
code within Rectangle is no big deal. There is one last factor I would
consider, and I honestly don't know what the answer is. Does a decent
compiler (g++, in my case) optimize away the function calls such as:

Point<T>& operator+=(const Point& p)
{
this->x += p.x;
this->y += p.y;
return *this;
}
?


--
___ _ ____ ___ __ __
/ _ )(_) / /_ __ / _ \___ _/ /_/ /____ ___
/ _ / / / / // / / ___/ _ `/ __/ __/ _ \/ _ \
/____/_/_/_/\_, / /_/ \_,_/\__/\__/\___/_//_/
/___/
Texas Instruments ASIC Circuit Design Methodlogy Group
Dallas, Texas, 214-480-4455, (e-mail address removed)
 
T

Thomas Matthews

Steven said:
The following may strike many of you as just plain silly, but it represents
the kind of delelima I find myself in when trying to make a design
decision. This really is a toy project written for the purpose of learning
to work with C++. It therefore makes some sense for me to give the
situation the amount of consideration presented below. To be quite honest,
I'm amazed at the amount there is to say about such a seemingly simple
situation. [snip]


Then I ask if Rectangle should derive from Point. There are arguments for
either choice. He suggests asking the question "can the object have two of
the things you are considering as a base class". At first I might say
"yes", I can have more than two points. I only need two points to define
the rectangle. That argues that the Rectangle should have Point members,
rather than be a Point. But what if we say a point isn't just a point,
it's a location. So we ask again, "can it have two?". Well, yes and no.
It can have many locations within it. Mathematically, there are an
infinite number of points in a rectangle. But the rectangle as a whole
only realistically has one position.
[snip]

You are asking more than just whether something should be a
class or not. You are asking about inheritance, member access,
and encapsulation.

By means of encapsulation, a rectangle is not a point. A rectangle
may consist of points, line segments, or vertices. Since there are
variations, the question then becomes, "What does somebody need
to know about a rectangle in order to use it?" This is where
the public interface comes into play.

We know that a rectangle has a height and a width and an area
(which is derived from height and width). We know that the
"top" and "bottom" lines must be parallel and so must the
side lines. This is the basic rectangle.

A rectangle may have a location. One could say that the
rectangle with a position is a new object. For sake of
simplicity, let it be that way (in this discussion).
So, how does one describe the location? The simplest
method is to pick a corner as an anchor then apply the
height with a direction and the width with a direction.
This says that a Rectangle_With_Location _is_a_ Rectangle
with: an anchor position,
a direction for the height,
and a direction for the width.

So far, I haven't specified what a location is. So far
encapsulation hasn't suggested a need to know in order
to use Rectangle_With_Location.

In order to achieve this abstract vision, we should give
the user's only the information they need to know. We
may implement the location as a Cartesian point. We
could implement the location using Polar coordinates
or some other system. If we give the clients access to
the data members, then we lose the ability to adapt our
Rectangle_With_Location to different constructs for
the location. We would _not_ want to provide an interface
that specifies the details of the location structure; for
that limits the usage of our class.

class Rectangle
{
public:
Distance get_width(void) const;
Distance get_height(void) const;
private:
Distance height;
Distance width;
};

class Rectangle_With_Location
: public Rectangle
{
public:
Rectangle_With_Location(const Location& anchor,
const Direction& height_direction,
const Distance& height,
const Direction& width_direction,
const Distance& width);
};


So, the basic theme is to keep things abstract until
they hit a level where specifics are required. Usually,
that involves some platform specifics.

--
Thomas Matthews

C++ newsgroup welcome message:
http://www.slack.net/~shiva/welcome.txt
C++ Faq: http://www.parashift.com/c++-faq-lite
C Faq: http://www.eskimo.com/~scs/c-faq/top.html
alt.comp.lang.learn.c-c++ faq:
http://www.comeaucomputing.com/learn/faq/
Other sites:
http://www.josuttis.com -- C++ STL Library book
 
S

Steven T. Hatton

Thomas said:
So, the basic theme is to keep things abstract until
they hit a level where specifics are required. Usually,
that involves some platform specifics.

Hmmm. That's the way I approached problems like this a few years back. I
believe it is a very good approach, and should have taken it. I guess part
of the reason I didn't is because I already have an idea of what the
presentation environment will look like. I'm actually working backwards
from there, to the more abstract. After I posted the original message I
took a look at how Trolltech does it. Interestingly, they took an approach
I didn't mention. Four distinct values rather than two points. Of course
they present the interface so that it can appear as four points, etc.

I find it a bit interesting that they store the actual coordinates as
opposed to a position and offset. I get the idea of position and offset
from `xterm -geometry 80x120+400+600'. The macro mangling they do for Mac
is also a bit interesting. Here's their header for QRect. I believe it is
the entire definition as well as declaration. Everything seems to be
inlined.

http://doc.trolltech.com/3.3/qrect-h.html
 
D

Daniel T.

Steven T. Hatton said:
The following may strike many of you as just plain silly, but it represents
the kind of delelima I find myself in when trying to make a design
decision. This really is a toy project written for the purpose of learning
to work with C++. It therefore makes some sense for me to give the
situation the amount of consideration presented below. To be quite honest,
I'm amazed at the amount there is to say about such a seemingly simple
situation.

I'm trying to learn to use C++ the way Stroustrup tells us to in TC++(PL).
He has a few different guidelines for determining if something should be
treated as a class or a struct (in the 'C' sense). One measure is whether
it has an invariant that needs to be preserved. So, say I have a rectangle
{x,y,w,h} where x and y are the coordinates of the top left corner, and w
and h are width and height respectively. I can arbitrarily change any
element of that set and still have a rectangle (assuming w > 0, h > 0, or I
assign meaning to the alternatives). Another criteria he has is whether
there are two possible representations of the same abstraction. If so, it
argues that it should be a class To that I say yes.

struct Rectangle{size_t x, y, w, h;};
struct Point{size_t x,y;};
struct RectanglePts{Point xy, dxdy;}

There are others:

struct Rectangle { int top, left, bottom, right; };
struct Rectangle { Point center; int height, width; };
struct Rectangle { Point top_left, bot_right; };

As is plain to see. There are many different representations of
Rectangle.

I may also want some convenience functions such as

Rectangle& Rectangle::widen(const size_t dw){ this->w += dw; return this; }
Rectangle& Rectangle::move(const Point dxy){
this->x += dxy.x;
this->y += dxy.y;
return this; }

Interestingly, there is an operation for which when an invariant can be
identified. The displacement between Rectangle::xy and Rectangle::dxdy
should be preserved under translations. Likewise scale operations should
preserve the ratio between the lengths of the sides.

class Rectangle {
public:
double area() const;
int top() const;
int left() const;
int bottom() const;
int right() const;
Point top_left() const;
Point top_right() const;
Point bot_left() const;
Point bot_right() const;
// many others...
};

There are many invariants that must be maintained among the above
methods. Notice, I haven't expressed any particular representation...

Then I ask if Rectangle should derive from Point. There are arguments for
either choice. He suggests asking the question "can the object have two of
the things you are considering as a base class". At first I might say
"yes", I can have more than two points. I only need two points to define
the rectangle. That argues that the Rectangle should have Point members,
rather than be a Point. But what if we say a point isn't just a point,
it's a location. So we ask again, "can it have two?". Well, yes and no.
It can have many locations within it. Mathematically, there are an
infinite number of points in a rectangle. But the rectangle as a whole
only realistically has one position.

Yes, a nice point.

void move( IT begin, IT end ) {
while ( begin != end ) {
begin->move_right( 5 );
++begin;
}
}

There is no reason why the above function should only work on Points, it
could just as easily work on Rectangles, Circles, &c. What do all these
things that the move function can act on have in common? If you had to
use one word to describe them all, would you use "Point"? I think not...
I have not completely definitive test to determine whether a rectangle
should be a class or a struct. Nor can I clearly decide if it should be a
Point and have another Point, or if it should have two points. I favor the
former for the reasons involving positioning.

There is no completely definitive test. That is one reason we use
classes and interfaces, because it allows for greater variation than a
simple struct does. So when in doubt, default to class.

But now, I have to consider a point. Can it have two representations? A
stupid {x,y} odered pair? Yup! {r cos(theta), r sin(theta)}. I even
briefly entertained the idea of inheriting from std::complex to get my
Point class.

But is it a class? It doesn't seem to have any other invariant than "it has
two components". But that /is/ an invariant that is determined when the
object is created. But that is true of any struct with two members.

class Point {
public:
int x() const;
int y() const;
double radius() const;
double angle() const;
};

Again, I have not commited to any particular representation, yet there
are invariants that must be maintained among the methods above...
 
S

Steven T. Hatton

Daniel said:
....

There are others:

struct Rectangle { int top, left, bottom, right; };
struct Rectangle { Point center; int height, width; };
struct Rectangle { Point top_left, bot_right; };

As is plain to see. There are many different representations of
Rectangle.

I'm not sure if the number of possible representations argues more strongly
for use of class over struct. I'll have to give that further
consideration.
class Rectangle {
public:
double area() const;
int top() const;
int left() const;
int bottom() const;
int right() const;
Point top_left() const;
Point top_right() const;
Point bot_left() const;
Point bot_right() const;
// many others...
};

There are many invariants that must be maintained among the above
methods. Notice, I haven't expressed any particular representation...

I have to ask how many of these invariants are 'orthogonal'. Which can be
derived from the other. To some extend, the is a question of how much work
I want my rectangle class to do for the user. My immediate situation does
not address the entire scope of what the discussion has become. But I
never really stated my requirements, and the implication was we are
designing some kind of graphics display software along the lines of QCanvas
from Qt. And that is how I'm treating things in this discussion.
Yes, a nice point.

void move( IT begin, IT end ) {
while ( begin != end ) {
begin->move_right( 5 );
++begin;
}
}

Believe it or not, the following Mathematica code from

/usr/local/Wolfram/Mathematica/5.0/AddOns/StandardPackages/Graphics/Shapes.m

does much the same thing, but is fully intrusive. They might be considered
the analog of C++ friend functions WRT the Rectangle class.

RotateShape[ shape_, phi_, theta_, psi_ ] :=
Block[{rotmat = RotationMatrix3D[N[phi], N[theta], N[psi]]},
shape /. { poly:polygon[_] :> Map[(rotmat . #)&, poly, {2}],
line:Line[_] :> Map[(rotmat . #)&, line, {2}],
point:point[_] :> Map[(rotmat . #)&, point,{1}] }
]

TranslateShape[shape_, vec_List] :=
Block[{tvec = N[vec]},
shape /. { poly:polygon[_] :> Map[(tvec + #)&, poly, {2}],
line:Line[_] :> Map[(tvec + #)&, line, {2}],
point:point[_] :> Map[(tvec + #)&, point,{1}] }
] /; Length[vec] == 3


AffineShape[ shape_, vec_List ] :=
Block[{tvec = N[vec]},
shape /. { poly:polygon[_] :> Map[(tvec * #)&, poly, {2}],
line:Line[_] :> Map[(tvec * #)&, line, {2}],
point:point[_] :> Map[(tvec * #)&, point,{1}] }
] /; Length[vec] == 3

And even more unbelievable, I can actually, more or less, understand it.

There are arguments for using member functions to perform the actual
manipulation, or using external 'helper' functions to manipulate the
objects. In either case, you need to provide the parameters for the
transformation.
There is no reason why the above function should only work on Points, it
could just as easily work on Rectangles, Circles, &c. What do all these
things that the move function can act on have in common? If you had to
use one word to describe them all, would you use "Point"? I think not...

I would use "position". But what's a position? Mathematically a position
and a point are synonymous. That still begs lots of questions. For
example, what do I want to choose as the definitive point when I talk about
position. For rectangles top left is a natural fit. For circles, center
seems to make more sense. For arbitrary polygons, I'd have to resort to
some kind of center of mass calculation to refer to the 'center'. That
might get expensive. Sometimes people use the notion of 'bounding
rectangle'. That choice is consistent with the current discussion, and
even though I might prefer the use of center in many circumstances, that
goes beyond the scope of the matter at hand.
There is no completely definitive test. That is one reason we use
classes and interfaces, because it allows for greater variation than a
simple struct does. So when in doubt, default to class.

class Point {
public:
int x() const;
int y() const;
double radius() const;
double angle() const;
};

Here we have another, and clearer case of properties that should remain
invariant under specific operations. Assuming the interface to the point
implies that it is a radial vector, the radius and and angle truly are
independent variables.

That brings me to another consideration for the rectangle. Given a
definition of vertical, and horizontal, and assuming our rectangle's sides
remain parallel to the axes of the rectangular coordinate system where
these are defined, we only need two ordered pairs of coordinate values (two
points) to fully describe the rectangle. Now the question becomes, should
the actual mathematical representation of the rectangle be separated from
the interface that presents it as a collection of all reasonable
combinations of descriptive values.

For purposes of manipulating the representation, I prefer the simplest
interface possible. Say, for example, I want to construct a grid of nested
rectangles. It's easier for me to think in terms of two points, one being
top left "position" the other being bottom right "extension". The way
things are working out, it seems I have a situation like this:

QCanvasRectangle will do all the drawing for me. It has a more abstract
(not in the sense of virtual functions) object called QRect, that I can
directly manipulate and query. That object serves as the Rectangle class
we have been discussing. This is the code for QRect
http://doc.trolltech.com/3.3/qrect-h.html I can use it as the Rectangle
we've been discussing.

Stroustrup tells us it is probably a good idea to separate our code from any
dependencies on specific third party libraries, as much as possible. See
the various discussions in Chapter 12 of TC++PL(SE). Taking that approach,
I want to identify the abstract essentials of what a rectangle means to me.
My approach is this:

I want to be able to set, and get the color of the rectangle, change some
text displayed in the rectangle, establish the size and position of the
rectangle, and, of course construct and destroy the rectangle. Caveat
emptor on this last operation. Someone else may own it.

So my view of the rectangle might be expressed as an interface that sets and
gets two points, a color, and some text. I also need to be sure the
rectangle is displayed, and refreshed when it is changed. Abstracting
these last operations may be more difficult than the manipulation of the
'hard data'. I need to post some code to describe what comes next. And I
need to write that code first. So...
 
J

Jerry Coffin

[ ... ]
Then I ask if Rectangle should derive from Point.

I suggest asking the question: "does this derivation satisfy the
Liskov Substitution Principle?" To wit, is it reasonable to substitute
a rectangle for a point under all possible circumstances? The answer
is clearly no: for example, saying a 2x3 unit rectangle is the center
of a 1 unit circle simply makes no sense.

That means you should NOT use public derivation in this case. Private
inheritance is still a possibility, but a rather poor one in this case
as well. Private inheritance is sometimes a reasonable alternative to
aggregation, but aggregation is generally preferred. Use private
inheritance when you need to, such as the base class containing a
virtual function you need to override. Otherwise, use aggregation.
There are arguments for either choice.

In this case I'd say there's almost no reasonable argument for
inheritance.
And it makes some sense to treat the rectangle as a position with a size and
shape. Rather than thinking of a Point as a pair of coordinates, or a
location, we might think of it as a geometric object. It could be
considered as the fundamental geometric object. We will never draw a
geometric object on a screen without it having a location. I can use the
same set of operations to move any geometric object, as I use to move a
point. I might even write some operators to add two points as vectors, and
then inherit these in other geometric objects. If I add a Point to a
rectangle it is equivalent to moving the rectangel such that the
coordinates of the top left corner have the components of the point added
to them.

I have not completely definitive test to determine whether a rectangle
should be a class or a struct. Nor can I clearly decide if it should be a
Point and have another Point, or if it should have two points. I favor the
former for the reasons involving positioning.

The LSP makes clear that rectangle should not derive from point. Your
paragraph above makes it clear that (for your purposes) point and
rectangle share some properties, but neither one can be substituted
for the other dependably.

Common operations but lack of substitution indicate a third
alternative: neither one derives from the other, but both derive from
a common base class. You've even pretty much specified this above:
both point and rectangle are geometric objects, so it would be quite
reasoanble to start with a (probably abstract) geometric object base
class, and derive both point and rectangle from it.

[ ... ]
So what's the advantage of protecting the components of Point? One argument
people give is that it prevents me from inadvertently modifying the data.
That seems a bit spurious to me in this case. Another, more reasonable
argument that Stroustrup doesn't mention is the notion of event
notification. If I change a value on Point, I may want to notify the
graphics rendering software of the change so it can schedule an update. If
I call a method to make the change, I can have the event fired as a side
effect.

IMO, it's a poor idea to add a feature just on the off chance that you
might someday want it. It's sensible to plan for the possibility, but
much less so to add the ability before you have a reason to.
So I make the components protected. So what? What dose that cost me? The
Point that I inherit in Rectangle acts just the way I want it. It inherits
everything from point that I need it to. OTOH, when I try to modify the
components of the lower right corner dxdy, I find I am not permitted to
access the protected data. So, either I use the public interface to modify
the data, or make Rectangle or some standalone function a friend of Point.
The former is a cyclic reference, and bad design, as a general rule. The
latter is something I find to be in bad taste.

You're seeing the beginning of the problems that are inevitable from
making rectangle inherit from point. If you attempt to continue along
that path, you'll quickly find that they get much worse.
If my public interface to point is well designed, writing that bit of extra
code within Rectangle is no big deal. There is one last factor I would
consider, and I honestly don't know what the answer is. Does a decent
compiler (g++, in my case) optimize away the function calls such as:

Okay, do you want the answer for a decent compiler, or for g++? :)

Seriously though, you're (apparently) trying to base design decisions
on a microscopic optimization in a single compiler. As we've all
heard, premature optimization is the root of all evil, and that's
about as premature as it can get.

Do your design so the DESIGN makes sense, then figure out how to make
it fast. Most clean designs end up fast anyway...
Point<T>& operator+=(const Point& p)
{
this->x += p.x;
this->y += p.y;
return *this;
}
?

I'd venture to guess that most C++ compilers could/would inline the
code for this -- but I wouldn't make a fundamental design decision
based on that anyway.
 
S

Steven T. Hatton

Jerry said:
[ ... ]
Then I ask if Rectangle should derive from Point.

I suggest asking the question: "does this derivation satisfy the
Liskov Substitution Principle?" To wit, is it reasonable to substitute
a rectangle for a point under all possible circumstances? The answer
is clearly no: for example, saying a 2x3 unit rectangle is the center
of a 1 unit circle simply makes no sense.

I don't follow the reasoning here. Suppose I have a class called Shape which
holds data relevant to all shapes in my program, e.g., color, position,
velocity, display state, etc. It would make sense to derive Rectangle from
that, if I am actually talking about a displayed graphical rectangle. It
would also make sense to derive Circle from the same base class. But this
derivation would fail this substitution test as well. I've never heard of
the Liskov Substitution Principle, but google turned this up:

http://c2.com/cgi/wiki?LiskovSubstitutionPrinciple

"What is wanted here is something like the following substitution property:
If for each object o1 of type S there is an object o2 of type T such that
for all programs P defined in terms of T, the behavior of P is unchanged
when o1 is substituted for o2 then S is a subtype of T."

I don't believe the argument you presented follows from that principle.

If S is Rectangle, and T is Circle, an instance o1 of Rectangle is not
interchangeable with an o2 that is an instance of Circle. Circle is not a
subtype of rectangle. But I didn't intend it to be. There /are/ graphics
programs that do derive all shapes from rectangle. In such cases, the
Rectangle is a bounding rectangle of the given shape.

I find the LSP to be a formal statement of the obvious.
In this case I'd say there's almost no reasonable argument for
inheritance.

Sure there are. I can translate a rectangle r1 by doing r1 += p1.

/* Point.hh */
#ifndef POINT_HH
#define POINT_HH

#include <iostream>
#include <cstddef> //size_t
#include <sth/Stringable.hh>

namespace sth{
using std::eek:stream;
/**
* Point represents an ordered pair.
*/
template <typename T>
class Point: public Stringable {

public:
Point<T>(const T& x_=0,
const T& y_=0)
: x(x_),
y(y_)
{}

const T& X() const { return this->x; }
const T& Y() const { return this->y; }

Point<T>& operator+=(const Point& p)
{
this->x += p.x;
this->y += p.y;
return *this;
}

Point<T>& dx(const T& dx_)
{
this->x += dx_;
return *this;
}

Point<T>& dy(const T& dy_)
{
this->x += dy_;
return *this;
}

virtual ostream& stringify(ostream& out) const
{
return out << "sth::point {" << x <<"," << y << "}";
}

protected:
T x;
T y;
};

template<typename T>
Point<T> operator+(const Point<T>& p1, const Point<T>& p2);

}

/* Point.cc */
#endif
#include "Point.hh"

namespace sth{
template<typename T>
Point<T> operator+(const Point<T>& p1, const Point<T>& p2)
{
return Point<T>(p1) += p2;
}
}

#ifndef RECTANGLE_HH
#define RECTANGLE_HH
#include "Point.hh"
#include <iostream>

namespace sth {
using std::eek:stream;

template <typename T>
class Rectangle: public Point<T>{

public:
Rectangle<T>(const Point<T>& xy=Point<T>(0,0),
const Point<T>& wh_=Point<T>(1,1))
: Point<T>(xy),
wh(wh_)
{}

const T& W() const { return this->wh.x; }
const T& H() const { return this->wh.y; }

Rectangle<T>& dw(const T& dw_) { return this->wh.dx(dw_); }
Rectangle<T>& dh(const T& dh_) { return this->wh.dy(dh_); }
Rectangle<T>& dwdh(const Point<T>& dwdh_) { return this->wh += dwdh_; }

ostream& stringify(ostream& out) const
{
out << "sth::Rectangle {xy=";
Point<T>::stringify(out);
out << ",wh=";
this->wh.stringify(out);
out << "}\n";
return out;
}
protected:
Point<T> wh;
};
}
#endif

Common operations but lack of substitution indicate a third
alternative: neither one derives from the other, but both derive from
a common base class. You've even pretty much specified this above:
both point and rectangle are geometric objects, so it would be quite
reasoanble to start with a (probably abstract) geometric object base
class, and derive both point and rectangle from it.

The degenerate case of geometric object is point.
[ ... ]
So what's the advantage of protecting the components of Point? One
argument people give is that it prevents me from inadvertently modifying
the data.
That seems a bit spurious to me in this case. Another, more reasonable
argument that Stroustrup doesn't mention is the notion of event
notification. If I change a value on Point, I may want to notify the
graphics rendering software of the change so it can schedule an update.
If I call a method to make the change, I can have the event fired as a
side effect.

IMO, it's a poor idea to add a feature just on the off chance that you
might someday want it. It's sensible to plan for the possibility, but
much less so to add the ability before you have a reason to.

I agree. That's why I decided to use a class with protected data, but
didn't bother to add any listener registiontion code, or event triggers.
You're seeing the beginning of the problems that are inevitable from
making rectangle inherit from point. If you attempt to continue along
that path, you'll quickly find that they get much worse.

If I consider a shape to be to be an extended geometric object defined
within its own local coordinate system, I could derive all objects from an
object called Basis, where Basis has two conceptual coordinate axes (or
three, if I'm doing 3D programming). Basis can also have a position vector
which specifies its location relative to another Basis, with the entire
object graph rooted by a Basis at the origin. I've done just that in both
Java3D and Mathematica. Once I figured out what it all really meant, I
found I could do a lot with it. But it was really redundant in Java3D
because my bases were synonymous with Java's TransformGroup (IIRC).

But that level of complexity is far beyond what I am trying to accomplish
with this exercise. If I collapse a few of the ideas into one or two
classes, I end up with the point being the analogue of a Basis, and the
rectangle being analogous to a basis with a subordinate basis. IOW, it is
a point with another point positioned relative to it. As such, the bottom
right point defining the extent of the Rectangle can be seen as a point
within the Rectangle's coordinate system which is manipulated from within
the Rectangle in the same way the Rectangle is manipulated(translated) in
the coordinate system in which it is embeded.

I could even derive a Circle from a Rectangle by imposing the restriciton
that the sides be invariantly equal, and defining the center as the bottom
right point.

Stroustrup argues that an elipse should not be derived from a circle because
an elipse is defined by two foci, and a circle just happens to be the
degenerate case where the foci cooincide. Well that is not the only way to
describe an elipse. You /can/ draw an elipse by using a radial vector
placed at the origin. So I can also derive an Elipse fromm my Rectangle,
and might even derive a Circle from the Elipse.
Okay, do you want the answer for a decent compiler, or for g++? :)

Seriously though, you're (apparently) trying to base design decisions
on a microscopic optimization in a single compiler. As we've all
heard, premature optimization is the root of all evil, and that's
about as premature as it can get.

Do your design so the DESIGN makes sense, then figure out how to make
it fast. Most clean designs end up fast anyway...

In §23.4.7 of TC++PL(SE) Stroustrup tells us "Donald Knuth observed that
'premature optimization is the root of all evil.' Some people have learned
that lesson all too well and condider all concern for efficiency evil. On
the contrary, efficiency must be kept in mind throughout the design and
implementation effort."
I'd venture to guess that most C++ compilers could/would inline the
code for this -- but I wouldn't make a fundamental design decision
based on that anyway.

In the case of complex simulations, it's a good idea to be sure you don't
put inheritnly inefficient code at the foundation. I'll grant that careful
consideration of interfaces can make it much easier to replace such
foundational objects with more efficient ones. But if you know one
approach is far more efficient than another, if efficiency is potentially a
significant consideration, its wise to beging with the more efficient
design. For example, Stroustup tells us that traversing a linked list
using a for loop is much more efficient than using recursion.

There are many instances in which I like to use recursion. That knowledge
is certain to have an impact on my future design decisions.
 
K

Kai-Uwe Bux

Steven said:
Jerry said:
[ ... ]
Then I ask if Rectangle should derive from Point.

I suggest asking the question: "does this derivation satisfy the
Liskov Substitution Principle?" To wit, is it reasonable to substitute
a rectangle for a point under all possible circumstances? The answer
is clearly no: for example, saying a 2x3 unit rectangle is the center
of a 1 unit circle simply makes no sense.

I don't follow the reasoning here. Suppose I have a class called Shape
which holds data relevant to all shapes in my program, e.g., color,
position,
velocity, display state, etc. It would make sense to derive Rectangle
from
that, if I am actually talking about a displayed graphical rectangle. It
would also make sense to derive Circle from the same base class. But this
derivation would fail this substitution test as well. I've never heard of
the Liskov Substitution Principle, but google turned this up:

http://c2.com/cgi/wiki?LiskovSubstitutionPrinciple

"What is wanted here is something like the following substitution
property: If for each object o1 of type S there is an object o2 of type T
such that for all programs P defined in terms of T, the behavior of P is
unchanged when o1 is substituted for o2 then S is a subtype of T."

I don't believe the argument you presented follows from that principle.

If S is Rectangle, and T is Circle, an instance o1 of Rectangle is not
interchangeable with an o2 that is an instance of Circle. Circle is not a
subtype of rectangle. But I didn't intend it to be. There /are/ graphics
programs that do derive all shapes from rectangle. In such cases, the
Rectangle is a bounding rectangle of the given shape.

I find the LSP to be a formal statement of the obvious.

Hi,


this LSP seems to be a convoluted way of the following test: Ask yourself,
is every rectangle a point? This is clearly not the case. Thus, do not
derive Rectangle from Point.

It appears that classes and inheritance are a good way to map the ontology
of your problem domain in an intuitive way to the type structure/hierarchy
of your code. A class D derived from B has all members of B but may add
more information and more methods. It is therefore more special. The
assignement

B b;
D d;
b = d;

is well formed. Every D is a B. The assignment

d = b;

is not well formed because there is no guarantee that b happens to be a D.

If Rectangle inherits from Point then you write your code based upon the
assumption that rectangles *are* points. Do you really want to do that?

Sure there are. I can translate a rectangle r1 by doing r1 += p1.

So what? Every shape can be translated.
The degenerate case of geometric object is point.

Yes, so if Rectangle inherits from Point all rectangles should be
degenerate. Assume Rectangle inherits from Point. Consider

Rectangle r ( ... ); // initialize a unit square
Point p;

p = r;
std::cout << r.Area() << " = " << p.Area() << " ?\n" << std::cout;

Methods that make sense for the base class should not without very good
reason completely change the meaning in derived classes.

Stroustrup argues that an elipse should not be derived from a circle
because an elipse is defined by two foci, and a circle just happens to be
the
degenerate case where the foci cooincide. Well that is not the only way
to
describe an elipse. You /can/ draw an elipse by using a radial vector
placed at the origin. So I can also derive an Elipse fromm my Rectangle,
and might even derive a Circle from the Elipse.

The intuitive reason not derive Ellipse from Circle is that not every
ellipse is a circle. The question whether it is wise to derive Circle from
Ellipse is more tricky.


Best

Kai-Uwe Bux
 
S

Steven T. Hatton

Kai-Uwe Bux said:
Steven T. Hatton wrote: [...]

Hi,


this LSP seems to be a convoluted way of the following test: Ask yourself,
is every rectangle a point? This is clearly not the case. Thus, do not
derive Rectangle from Point.

It appears that classes and inheritance are a good way to map the ontology
of your problem domain in an intuitive way to the type structure/hierarchy
of your code. A class D derived from B has all members of B but may add
more information and more methods. It is therefore more special. The
assignement

B b;
D d;
b = d;

But you are slicing there.

I believe a more realistic test would be can I do:

Rectangle r.

void f(Point& p){...}

f(r);

and get reasonable results when functions from Point are invoked on
Rectangle? And the answer is yes. Every function from Point in Rectangle
other than

ostream& Point::stringify(ostream& out)

behaves exactly as it does when called on a Point object.

So what? Every shape can be translated.

So every shape is derivable from Point.

Yes, so if Rectangle inherits from Point all rectangles should be
degenerate. Assume Rectangle inherits from Point. Consider

Rectangle r ( ... ); // initialize a unit square
Point p;

p = r;
std::cout << r.Area() << " = " << p.Area() << " ?\n" << std::cout;

Methods that make sense for the base class should not without very good
reason completely change the meaning in derived classes.

Well, I didn't put an area function on Point, but it could easily be done
with meaning. Area == 0; I might have considered making it pure virtual,
but that would make Point pure virtual, and I have a use for instantiating
Point, and a reasonalbe return value for the area of a point.
The intuitive reason not derive Ellipse from Circle is that not every
ellipse is a circle.

Yes, I believe I stated that backwards.
 
K

Kai-Uwe Bux

Steven said:
Kai-Uwe Bux said:
Steven T. Hatton wrote: [...]

Hi,


this LSP seems to be a convoluted way of the following test: Ask
yourself, is every rectangle a point? This is clearly not the case. Thus,
do not derive Rectangle from Point.

It appears that classes and inheritance are a good way to map the
ontology of your problem domain in an intuitive way to the type
structure/hierarchy of your code. A class D derived from B has all
members of B but may add more information and more methods. It is
therefore more special. The assignement

B b;
D d;
b = d;

But you are slicing there.

What is slicing? I am just pointing out that the syntax of C++ clearly
indicates that every D is a B but not vice versa.

I believe a more realistic test would be can I do:

Rectangle r.

void f(Point& p){...}

You are using a reference? In C++, you should base the decision whether to
derive or upon how this decision fits within the semantics of the language
as a whole. Assignments like b = d are legal, and f(Point p) can be called
on rectangles, too.
f(r);

and get reasonable results when functions from Point are invoked on
Rectangle? And the answer is yes. Every function from Point in Rectangle
other than

ostream& Point::stringify(ostream& out)

behaves exactly as it does when called on a Point object.

That just indicates that your Points are not really full fledged points.
Clearly you are missing a lot of possible methods that would make a
difference. What about, for instance, a method p.draw()?
So every shape is derivable from Point.



Well, I didn't put an area function on Point, but it could easily be done
with meaning. Area == 0; I might have considered making it pure virtual,
but that would make Point pure virtual, and I have a use for instantiating
Point, and a reasonalbe return value for the area of a point.

Now, I think the whole problem is that you named the base class Point in a
bad way: it should have been called LocatedShape or something like this.
You are using the two coordinates of your Point class only for the
reference point indicating the location of classes derived from Point. The
name Point, however, suggests that the class would allow for methods like
Area() or draw(), which clearly yield absurd results.

In short, that these methods are missing, tells me that your Point class is
more general than a Point class should be. There is nothing wrong with
inheriting BasepointedRectangle (given by lower left corner, width, height
) from something like BasepointedShape (given by x and y coordinates) and
AbstractRectangle (width and height only).

The real question is how you want to map the ontology of your problem
domain into your code. And questions about ontological relations are of the
type is every D a B?


Best

Kai-Uwe Bux
 
L

lilburne

Steven said:
I don't believe the argument you presented follows from that principle.

If S is Rectangle, and T is Circle, an instance o1 of Rectangle is not
interchangeable with an o2 that is an instance of Circle. Circle is not a
subtype of rectangle. But I didn't intend it to be. There /are/ graphics
programs that do derive all shapes from rectangle. In such cases, the
Rectangle is a bounding rectangle of the given shape.

Of course you can't substitute a rectangle for a circle or a circle for
a rectangle, but you can substitute a rectangle for a shape and a circle
for a shape. Both rectangle and circle are S of T (shape).


I find the LSP to be a formal statement of the obvious.




Sure there are. I can translate a rectangle r1 by doing r1 += p1.

You can't translate anything by a point. You can translate it by a
vector, but a point isn't a vector. Whilst you can 'release' a point to
create a vector, or 'anchor' a vector to create a point, the two things
are not the same.
The degenerate case of geometric object is point.

Hmm I tried that argument on my mathematicians a couple of years ago,
when I wanted to treat a degenerate curve as a point. I was beaten up
something horrid, I recall the most favourably description of the scheme
was that it was a 'bastardization'.
 
D

Daniel T.

Steven T. Hatton said:
I'm not sure if the number of possible representations argues more strongly
for use of class over struct. I'll have to give that further
consideration.

I thought you already were. From your OP:
I'm trying to learn to use C++ the way Stroustrup tells us to in TC++(PL).
He has a few different guidelines for determining if something should be
treated as a class or a struct (in the 'C' sense). One measure is whether
it has an invariant that needs to be preserved. So, say I have a rectangle
{x,y,w,h} where x and y are the coordinates of the top left corner, and w
and h are width and height respectively. I can arbitrarily change any
element of that set and still have a rectangle (assuming w > 0, h > 0, or I
assign meaning to the alternatives). Another criteria he has is whether
there are two possible representations of the same abstraction.

As you rightly point out above, Stroustrup believes that multiple
representations mean we should use a class. I'm pointing out that there
are multiple representations...

I have to ask how many of these invariants are 'orthogonal'. Which can be
derived from the other.

It doesn't matter. All of the methods above represent properties of a
Rectangle and there are definite invariants that exist between some of
them.

To some extend, the is a question of how much work
I want my rectangle class to do for the user.

The answer to that should be "all the rectangle work".

There are arguments for using member functions to perform the actual
manipulation, or using external 'helper' functions to manipulate the
objects.

Absolutly, and for class that have no virtual functions there is little
difference...

Rect { int x, y, h, w };

void move( Rect& r, int dx, int dy ) {
r.x += dx;
r.y += dy;
}

isn't much different than:

class Rect {
int x, y, h, w;
public:
void move( int dx, int dy ) {
x += dx;
y += dy;
}
};

Except that with the latter, you can more esily change the 'int x, y, h,
w' to (for example) 'int top, left, bottom, right'.

I would use "position".

Position (n): the point or area occupied by a physical object.

Is the "point or area occupied by a physical object" synonymous to/a
generalization of "Rectangle"? I don't think so.

For
example, what do I want to choose as the definitive point when I talk about
position.

Certanly the 'move' function we are describing doesn't care.

Stroustrup tells us it is probably a good idea to separate our code from any
dependencies on specific third party libraries, as much as possible. See
the various discussions in Chapter 12 of TC++PL(SE).

I would say the best way to do this is the following. (a) find two third
party libraries that have the functionality needed (b) write your code
in such a way that you can switch from one library to the other with the
fewest changes.

Taking that approach,
I want to identify the abstract essentials of what a rectangle means to me.
My approach is this:

I want to be able to set, and get the color of the rectangle, change some
text displayed in the rectangle, establish the size and position of the
rectangle, and, of course construct and destroy the rectangle. Caveat
emptor on this last operation. Someone else may own it.

class Rectangle {
// private stuff
public:
void setColor( const Color& c );
const Color getColor() const;
void changeText( const std::string& s );
void setCenterPostion( const Point& p );
void setSize( unsigned width, unsigned height );
};

the above class does everything you need, but says nothing to the
outside world about what its internal representation is. The internal
representation could be any of the 6 we discussed at the top of this
post or something different.
 
T

Thomas Matthews

Steven said:
Jerry Coffin wrote:

[ ... ]

Then I ask if Rectangle should derive from Point.

I suggest asking the question: "does this derivation satisfy the
Liskov Substitution Principle?" To wit, is it reasonable to substitute
a rectangle for a point under all possible circumstances? The answer
is clearly no: for example, saying a 2x3 unit rectangle is the center
of a 1 unit circle simply makes no sense.


I don't follow the reasoning here. Suppose I have a class called Shape which
holds data relevant to all shapes in my program, e.g., color, position,
velocity, display state, etc. It would make sense to derive Rectangle from
that, if I am actually talking about a displayed graphical rectangle. It
would also make sense to derive Circle from the same base class. But this
derivation would fail this substitution test as well. I've never heard of
the Liskov Substitution Principle, but google turned this up:

Mathematically, a point has no area. A rectangle has an area.

One can draw a line between two points. In drawing a line between
two rectangles, where does one connect the lines?

One can calculate a distance between two points. How does one calculate
the distance between two non-collinear rectangles?

Although one could _use_ a rectangle to represent a point, you have
a circular reference. If a rectangle is represented by four points,
and yet is a point, where does it end?

You should consider that an implementation of a rectangle contains
points, it is _not_ a point. The phrase is simplified to a
rectangle "has-a" point. The "has-a" relationship is a very good
indicator of containment versus the "is-a" relationship for
inheritance.

The C++ FAQ has a good discussion about circles, elipses inheritance
and containment.

Also, just because a rectangle can be a point[1] does not mean it
should be one.

----
[1] Many computer systems draw lines using rectangles. As the line
of rectangles is viewed from a far distance, it looks like a
a line. Rectangles _can_ be used as points.

--
Thomas Matthews

C++ newsgroup welcome message:
http://www.slack.net/~shiva/welcome.txt
C++ Faq: http://www.parashift.com/c++-faq-lite
C Faq: http://www.eskimo.com/~scs/c-faq/top.html
alt.comp.lang.learn.c-c++ faq:
http://www.comeaucomputing.com/learn/faq/
Other sites:
http://www.josuttis.com -- C++ STL Library book
 
J

Jerry Coffin

[ ... ]
I don't follow the reasoning here. Suppose I have a class called Shape which
holds data relevant to all shapes in my program, e.g., color, position,
velocity, display state, etc. It would make sense to derive Rectangle from
that, if I am actually talking about a displayed graphical rectangle. It
would also make sense to derive Circle from the same base class. But this
derivation would fail this substitution test as well.

Unless you define your Shape class very oddly, this design would pass
the substitution test perfectly -- in fact, it's pretty much what I
suggested.
I've never heard of
the Liskov Substitution Principle, but google turned this up:

I'd do some more looking, and read about it in more detail. For most
practical purposes, it's the single principle that guides all use of
derivation.
http://c2.com/cgi/wiki?LiskovSubstitutionPrinciple

"What is wanted here is something like the following substitution property:
If for each object o1 of type S there is an object o2 of type T such that
for all programs P defined in terms of T, the behavior of P is unchanged
when o1 is substituted for o2 then S is a subtype of T."

I don't believe the argument you presented follows from that principle.

Whoever wrote that definition seems to have specialized in
obfuscation, but from a practical viewpoint, following the LSP means
that you can substitute a derived object for a base object under any
possible circumstances.
If S is Rectangle, and T is Circle, an instance o1 of Rectangle is not
interchangeable with an o2 that is an instance of Circle. Circle is not a
subtype of rectangle. But I didn't intend it to be. There /are/ graphics
programs that do derive all shapes from rectangle. In such cases, the
Rectangle is a bounding rectangle of the given shape.

There are many examples of truly horrible designs in the world. That
doesn't make it a good idea to add another.
I find the LSP to be a formal statement of the obvious.

In that case you're advocating designs that obviously wrong.
Sure there are. I can translate a rectangle r1 by doing r1 += p1.

IMO, you seem to be progressing from bad to worse. Adding a point to a
polygon should do exactly that: add a point to the polygon, normally
resulting in a polygon with one more vertex.
The degenerate case of geometric object is point.

Degenerate cases rarely mean much, at least when you're talking about
derivation. It's true (for example) that a rectangle with sides of
length 0 is really a single point, an example proving your point above
valid. That means that a rectangle CAN be a point (as can a circle,
etc.) To be meaningful from a viewpoint of derivation, however, you
have to show that EVERY rectangle is a point, every circle is a point,
etc.

That's not the case, and a hierarchy that assumes it is will cause
almost nothing but problems.

Keep in mind that in the end there are two basic ideas here: we want
the compiler to allow the things that make sense, but disallow (short
of things like casting to force it to allow them) things that don't
really make sense.

The question, then, is whether you want to be able to pass a
rectangle, circle, etc., to every function, every place, that a point
is accepted. Regardless of what you think that's what the LSP states,
that IS what the compiler allows: if I have code like this:

class base {};
class derived : public base {};

void func(const base &b) {
}

then I can pass an instance of derived to func without any casting,
etc. I.e. the compiler assumes that when I use derivation, that I mean
the derived object can be substituted for the base class object.

[ ... ]
I could even derive a Circle from a Rectangle by imposing the restriciton
that the sides be invariantly equal, and defining the center as the bottom
right point.

You _could_ do almost anything -- but what you're advocating here will
lead to NOTHING but problems.
Stroustrup argues that an elipse should not be derived from a circle because
an elipse is defined by two foci, and a circle just happens to be the
degenerate case where the foci cooincide. Well that is not the only way to
describe an elipse. You /can/ draw an elipse by using a radial vector
placed at the origin. So I can also derive an Elipse fromm my Rectangle,
and might even derive a Circle from the Elipse.

Deriving circle from ellipse is almost always a bad idea. Public
derivation means that the derived class has the full interface of the
base class. In the case of an ellipse base class, you're typically
going to have some way of setting the major and minor axes to
different values. If circle derives from ellipse, it's promising to do
the same -- but doing so would give an ellipse instead of a circle.

The bottom line: from a viewpoint of substitutability, a circle is not
an ellipse and an ellipse is not a circle. Any attempt at deriving one
from the other will normally result in problems.

[ ... ]
In §23.4.7 of TC++PL(SE) Stroustrup tells us "Donald Knuth observed that
'premature optimization is the root of all evil.' Some people have learned
that lesson all too well and condider all concern for efficiency evil. On
the contrary, efficiency must be kept in mind throughout the design and
implementation effort."

Good for him. In this case, he's mostly wrong. The fact is that most
people who try to take efficiency into account early in the design end
up causing themselves problems. First of all, they're usually wrong
about what to optimize, so even at best their attempt does no good.
Second, in their zeal for optimizing the wrong things, they exacerbate
the real problem. Finally, they typically make the interface too low
of level, so it's almost impossible to hide the complex parts
internally, so the real performance problems become difficult to fix.
In the case of complex simulations, it's a good idea to be sure you don't
put inheritnly inefficient code at the foundation.

Regardless of the type of program, there's no point in introducing
needless inefficiency. Nonetheless, if you start with a good
algorithm, express it cleanly, and provide a clean interface, chances
are roughly 99% that your code will not only be fast enough, but that
it'll be faster than most of the other code out that that was
"optimized" much more carefully.

[ ... ]
For example, Stroustup tells us that traversing a linked list
using a for loop is much more efficient than using recursion.

There are many instances in which I like to use recursion. That knowledge
is certain to have an impact on my future design decisions.

My advice would be simpler: use recursion when it improves readability
and understandability, but not otherwise. Certainly, use iteration to
walk a linked list, but do it because it's more understandable. If
you're coding a tree traversal, it's rarely worthwhile to do it
iteratively -- and when it is, it's NOT a question of recursion vs.
iteration per se. It's (for example) when you're talking a directory
tree, and recursion gives you a depth-first traversal which is
generally substantially slower than a width-first traversal, which
happens to be easier to to iteratively.
 
J

Jerry Coffin

[ ... ]
I don't follow the reasoning here. Suppose I have a class called Shape which
holds data relevant to all shapes in my program, e.g., color, position,
velocity, display state, etc. It would make sense to derive Rectangle from
that, if I am actually talking about a displayed graphical rectangle. It
would also make sense to derive Circle from the same base class. But this
derivation would fail this substitution test as well.

Unless you define your Shape class very oddly, this design would pass
the substitution test perfectly -- in fact, it's pretty much what I
suggested.
I've never heard of
the Liskov Substitution Principle, but google turned this up:

I'd do some more looking, and read about it in more detail. For most
practical purposes, it's the single principle that guides all use of
derivation.
http://c2.com/cgi/wiki?LiskovSubstitutionPrinciple

"What is wanted here is something like the following substitution property:
If for each object o1 of type S there is an object o2 of type T such that
for all programs P defined in terms of T, the behavior of P is unchanged
when o1 is substituted for o2 then S is a subtype of T."

I don't believe the argument you presented follows from that principle.

Whoever wrote that definition seems to have specialized in
obfuscation, but from a practical viewpoint, following the LSP means
that you can substitute a derived object for a base object under any
possible circumstances.
If S is Rectangle, and T is Circle, an instance o1 of Rectangle is not
interchangeable with an o2 that is an instance of Circle. Circle is not a
subtype of rectangle. But I didn't intend it to be. There /are/ graphics
programs that do derive all shapes from rectangle. In such cases, the
Rectangle is a bounding rectangle of the given shape.

There are many examples of truly horrible designs in the world. That
doesn't make it a good idea to add another.
I find the LSP to be a formal statement of the obvious.

In that case you're advocating designs that obviously wrong.
Sure there are. I can translate a rectangle r1 by doing r1 += p1.

IMO, you seem to be progressing from bad to worse. Adding a point to a
polygon should do exactly that: add a point to the polygon, normally
resulting in a polygon with one more vertex.
The degenerate case of geometric object is point.

Degenerate cases rarely mean much, at least when you're talking about
derivation. It's true (for example) that a rectangle with sides of
length 0 is really a single point, an example proving your point above
valid. That means that a rectangle CAN be a point (as can a circle,
etc.) To be meaningful from a viewpoint of derivation, however, you
have to show that EVERY rectangle is a point, every circle is a point,
etc.

That's not the case, and a hierarchy that assumes it is will cause
almost nothing but problems.

Keep in mind that in the end there are two basic ideas here: we want
the compiler to allow the things that make sense, but disallow (short
of things like casting to force it to allow them) things that don't
really make sense.

The question, then, is whether you want to be able to pass a
rectangle, circle, etc., to every function, every place, that a point
is accepted. Regardless of what you think that's what the LSP states,
that IS what the compiler allows: if I have code like this:

class base {};
class derived : public base {};

void func(const base &b) {
}

then I can pass an instance of derived to func without any casting,
etc. I.e. the compiler assumes that when I use derivation, that I mean
the derived object can be substituted for the base class object.

[ ... ]
I could even derive a Circle from a Rectangle by imposing the restriciton
that the sides be invariantly equal, and defining the center as the bottom
right point.

You _could_ do almost anything -- but what you're advocating here will
lead to NOTHING but problems.
Stroustrup argues that an elipse should not be derived from a circle because
an elipse is defined by two foci, and a circle just happens to be the
degenerate case where the foci cooincide. Well that is not the only way to
describe an elipse. You /can/ draw an elipse by using a radial vector
placed at the origin. So I can also derive an Elipse fromm my Rectangle,
and might even derive a Circle from the Elipse.

Deriving circle from ellipse is almost always a bad idea. Public
derivation means that the derived class has the full interface of the
base class. In the case of an ellipse base class, you're typically
going to have some way of setting the major and minor axes to
different values. If circle derives from ellipse, it's promising to do
the same -- but doing so would give an ellipse instead of a circle.

The bottom line: from a viewpoint of substitutability, a circle is not
an ellipse and an ellipse is not a circle. Any attempt at deriving one
from the other will normally result in problems.

[ ... ]
In §23.4.7 of TC++PL(SE) Stroustrup tells us "Donald Knuth observed that
'premature optimization is the root of all evil.' Some people have learned
that lesson all too well and condider all concern for efficiency evil. On
the contrary, efficiency must be kept in mind throughout the design and
implementation effort."

Good for him. In this case, he's mostly wrong. The fact is that most
people who try to take efficiency into account early in the design end
up causing themselves problems. First of all, they're usually wrong
about what to optimize, so even at best their attempt does no good.
Second, in their zeal for optimizing the wrong things, they exacerbate
the real problem. Finally, they typically make the interface too low
of level, so it's almost impossible to hide the complex parts
internally, so the real performance problems become difficult to fix.
In the case of complex simulations, it's a good idea to be sure you don't
put inheritnly inefficient code at the foundation.

Regardless of the type of program, there's no point in introducing
needless inefficiency. Nonetheless, if you start with a good
algorithm, express it cleanly, and provide a clean interface, chances
are roughly 99% that your code will not only be fast enough, but that
it'll be faster than most of the other code out that that was
"optimized" much more carefully.

[ ... ]
For example, Stroustup tells us that traversing a linked list
using a for loop is much more efficient than using recursion.

There are many instances in which I like to use recursion. That knowledge
is certain to have an impact on my future design decisions.

My advice would be simpler: use recursion when it improves readability
and understandability, but not otherwise. Certainly, use iteration to
walk a linked list, but do it because it's more understandable. If
you're coding a tree traversal, it's rarely worthwhile to do it
iteratively -- and when it is, it's NOT a question of recursion vs.
iteration per se. It's (for example) when you're talking a directory
tree, and recursion gives you a depth-first traversal which is
generally substantially slower than a width-first traversal, which
happens to be easier to to iteratively.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,577
Members
45,054
Latest member
LucyCarper

Latest Threads

Top