How would Mr. Stroustrup implement his solution to 12.7[2]

O

Oplec

Hi,

I am learning standard C++ as a hobby. The C++ Programming Language :
Special Edition has been the principal source for my information. I read the
entirety of the book and concluded that I had done myself a disservice by
having not attempted and completed the exercises. I intend to rectify that.

My current routine is to read a chapter and then attempt every exercise
problem, and I find that this process is leading to a greater understanding
of the language and implementation.

However, occasionly I am unable to complete some of the exercises, or am not
satisfied with my method of solution. One such exercise is 12.7[2].

From what I read in the chapter and a few papers of his, Mr. Stroustrup
suggests that most of the time the best implemention for the Shape, Dot,
Line, Rectangle hierarchy would be for Shape to be a purely abstract base
class, and for Dot, Line, Rectangle to inheirit from Shape and implement the
virtual functions. In his code examples he routinely defines "virtual void
draw() = 0" as a member of the Shape class. Upon implementing a solution to
12.7[2] I experienced a difficulty surrounding how to implement the drawing
of Shapes for the Window class that must exist. My implementation was for
simple console mode character output using std::cout.

Since I am new to OO programming, I feel that I lack some understanding of
what he intended my solution to be similar to. I have read that it is good
to keep separate ideas, separate. And, using this idea, I run into
difficulty on how to have Dots, Lines, Rectangles, draw themselves, since
they need access to a method of rendering to the screen, but the rendering
must be controlled because I am using console mode character output. Also,
from reading 12.7[5], it seems that the rendering of the shapes needs to
controlled by an intermediary object so that the clipping can be performed.
I am at a loss as to how to complete this exercise in a properly
object-oriented fashion. I wish to understand how to implement C++ in the
best possible way, and that is why I am not satisfied with my current
understanding. If you have the time, and the experience, please share you
knowledge in this matter.

The following is my basic design used to complete 12.7[2], but what I feel
is a very poor design and should not be accepted as a solution. I offer it
in hopes that experienced programmers may better understand my problem, and,
perhaps, offer a better solution. Only the classes Point, Shape, Dot, and
Window will be shown, as their interaction is the same as that for Line, and
Rectangle.

(This is my first post, and I'm not sure how to include a compressed file of
my solution, so please accept this.)

Shape was kept purely abstract, but I added "virtual void draw(Window*
const) const = 0;". I added this because upon implementation I ran into the
problem of how the shapes would be able to access a method of rending to the
screen. What happens is that a Window object, w, has its "draw(Shape*)"
member called. Window::draw(Shape*) then passes its "this" pointer to
"Shape::draw(Window*)". The Shape::draw member then uses the Window pointer
to access Window::put_pixel(Point, char). Window::put_pixel(Point, char)
then adds a pair<Point, char> to a vector object, which is used when
Window::show() is called. The vector is used as a sort of buffer for the
shapes to be drawn to so that multiple shapes can be drawn correctly since I
am using a console mode and cout to render characters.

To draw something, the following would be used:

Window w(10,10);
w.draw(Dot(Point(5,5));
w.show();

//***************************
// Point.h
// This is a structure because Mr. Stroustrup suggests that classes
// with hidden members, and access members should only be
// used when there is an invariant to enforce, and Point
// does not have an invariant.
// **************************
#ifndef POINT_H
#define POINT_H

struct Point {
Point() :x(0), y(0) { }
Point(int i, int j) :x(i), y(j) { }

int x, y;
};

#endif



// ***************************
// Shape.h
// Mr. Stroustrup usually declares
// virtual void draw() = 0;
// My design has to pass a Window object.
// ***************************
#ifndef SHAPE_H
#define SHAPE_H

#include "point.h"

class Window;

struct Shape {
virtual Point n() const = 0;
virtual Point e() const = 0;
virtual Point s() const = 0;
virtual Point w() const = 0;
virtual Point ne() const = 0;
virtual Point nw() const = 0;
virtual Point se() const = 0;
virtual Point sw() const = 0;
virtual ~Shape() {}

virtual void draw(Window* const) const = 0;
};

#endif



// ******************************
// Dot.h
// ******************************
#ifndef DOT_H
#define DOT_H

#include "point.h"
#include "shape.h"

class Window;

class Dot : public Shape {
public:
Dot();
Dot(const Point&);

Point n() const;
Point e() const;
Point s() const;
Point w() const;
Point ne() const;
Point nw() const;
Point se() const;
Point sw() const;

void draw(Window* const) const;

private:
Point p_;
};

// Inline definitions
inline Point Dot::n() const { return p_; }

inline Point Dot::e() const { return p_; }

inline Point Dot::s() const { return p_; }

inline Point Dot::w() const { return p_; }

inline Point Dot::ne() const { return p_; }

inline Point Dot::nw() const { return p_; }

inline Point Dot::se() const { return p_; }

inline Point Dot::sw() const { return p_; }

#endif



// ********************
// Dot.cpp
// ********************
#include "dot.h"
#include "window.h"

Dot::Dot() {}

Dot::Dot(const Point& p)
: p_(p)
{
}

void Dot::draw(Window* const w) const
{
w->put_pixel(p_, '*');
}




// ****************************
// Window.h
// ****************************
#ifndef WINDOW_H
#define WINDOW_H

#include "point.h"
#include "utility"
#include "vector"

class Shape;

class Window {
public:
Window(const int, const int);

void draw(const Shape&);
Point current() const;
void current(const Point&);
void put_pixel(const Point&, const char&);
void show() const;
void clear();

private:
int w_, h_;
Point c_;
std::vector<std::pair<Point, char> > points;
};

#endif




// *************************
// Window.cpp
// *************************
#include "window.h"
#include "shape.h"
#include <iostream>

Window::Window(const int x, const int y)
: w_(x), h_(y) { }

void Window::draw(const Shape& s)
{
s.draw(this);
c_ = s.se();
}

Point Window::current() const
{
return c_;
}

void Window::current(const Point& c)
{
c_ = c;
}

void Window::put_pixel(const Point& p, const char& c)
{
points.push_back(std::make_pair(p, c));
}

// This searches the vector for each character position of the window.
// If a character is found, it is written to the screen.
// If a character is not found, a space is written to the screen.
void Window::show() const
{
for (int j=0; j <= h_; j++) {
for (int i=0; i <= w_; i++) {
typedef std::vector<std::pair<Point,char> >::const_iterator CI;
CI k;
for (k=points.begin(); k != points.end(); ++k) {
if (k->first.x == i && k->first.y == j) {
std::cout << k->second;
break;
}
}
if (k == points.end()) std::cout << ' ';
}
std::cout << '\n';
}
}

void Window::clear()
{
points.clear();
}
 
D

David White

Oplec said:
Hi,

I am learning standard C++ as a hobby. The C++ Programming Language :
Special Edition has been the principal source for my information. I read the
entirety of the book and concluded that I had done myself a disservice by
having not attempted and completed the exercises. I intend to rectify that.

My current routine is to read a chapter and then attempt every exercise
problem, and I find that this process is leading to a greater understanding
of the language and implementation.

However, occasionly I am unable to complete some of the exercises, or am not
satisfied with my method of solution. One such exercise is 12.7[2].

From what I read in the chapter and a few papers of his, Mr. Stroustrup
suggests that most of the time the best implemention for the Shape, Dot,
Line, Rectangle hierarchy would be for Shape to be a purely abstract base
class, and for Dot, Line, Rectangle to inheirit from Shape and implement the
virtual functions. In his code examples he routinely defines "virtual void
draw() = 0" as a member of the Shape class. Upon implementing a solution to
12.7[2] I experienced a difficulty surrounding how to implement the drawing
of Shapes for the Window class that must exist. My implementation was for
simple console mode character output using std::cout.

Since I am new to OO programming, I feel that I lack some understanding of
what he intended my solution to be similar to. I have read that it is good
to keep separate ideas, separate. And, using this idea, I run into
difficulty on how to have Dots, Lines, Rectangles, draw themselves, since
they need access to a method of rendering to the screen, but the rendering
must be controlled because I am using console mode character output. Also,
from reading 12.7[5], it seems that the rendering of the shapes needs to
controlled by an intermediary object so that the clipping can be performed.
I am at a loss as to how to complete this exercise in a properly
object-oriented fashion. I wish to understand how to implement C++ in the
best possible way, and that is why I am not satisfied with my current
understanding. If you have the time, and the experience, please share you
knowledge in this matter.

The following is my basic design used to complete 12.7[2], but what I feel
is a very poor design and should not be accepted as a solution. I offer it
in hopes that experienced programmers may better understand my problem, and,
perhaps, offer a better solution. Only the classes Point, Shape, Dot, and
Window will be shown, as their interaction is the same as that for Line, and
Rectangle.

(This is my first post, and I'm not sure how to include a compressed file of
my solution, so please accept this.)

Shape was kept purely abstract, but I added "virtual void draw(Window*
const) const = 0;". I added this because upon implementation I ran into the
problem of how the shapes would be able to access a method of rending to the
screen.

That's reasonable. In a real graphics system you want to be able to render a
drawing on different devices - any window, a printer, a plotter etc., but
you don't want the shape object itself to be tied down to a single device.
Passing in the device to draw on is therefore a good idea.
What happens is that a Window object, w, has its "draw(Shape*)"
member called. Window::draw(Shape*) then passes its "this" pointer to
"Shape::draw(Window*)".

I don't think this is such a good idea. Since the Shape object is doing the
drawing, there's no need for the Window class to know about Shapes. Just
pass a reference to the Window object to the Shape's draw(), and the
overridden function in the specific shape will draw itself on the Window.
The Shape::draw member then uses the Window pointer
to access Window::put_pixel(Point, char). Window::put_pixel(Point, char)
then adds a pair<Point, char> to a vector object, which is used when
Window::show() is called. The vector is used as a sort of buffer for the
shapes to be drawn to so that multiple shapes can be drawn correctly since I
am using a console mode and cout to render characters.

That all sounds good to me.
To draw something, the following would be used:

Window w(10,10);
w.draw(Dot(Point(5,5));
w.show();

I would have:

Window w(10,10);
Dot dot(Point(5,5));
dot.draw(w);

[most code snipped]

One comment on the code:
void draw(Window* const) const;
Window(const int, const int);

You can make your function parameters const if you want, but it's not usual
or necessary. Parameters are passed by value, so functions have their own
copies of them. It doesn't help the caller of a function know that a
function isn't going to modify its own copy of what's passed to it. The
caller doesn't care.

If you haven't done OO before and you are inexperienced in C++, I think
you're doing pretty well.

DW
 
O

Oplec

----- Original Message -----
From: "David White" <[email protected]>
Newsgroups: comp.lang.c++
Sent: Tuesday, October 21, 2003 8:40 PM
Subject: Re: How would Mr. Stroustrup implement his solution to 12.7[2]

I don't think this is such a good idea. Since the Shape object is doing the
drawing, there's no need for the Window class to know about Shapes. Just
pass a reference to the Window object to the Shape's draw(), and the
overridden function in the specific shape will draw itself on the Window.

The Window class has to have a draw() member because such a member is
included in the example use of the final implementation of problem 12.7[2].
He shows that a Window, w, should be used as in the following:
w.draw(Circle(w.current(),10)); So, clearly, he intends for the solutions'
Window class to have a draw() member that takes a pointer to a Shape class.
I completely understand your idea, and would do it that way as well, but I
wish to complete this the way he intended it to be. One of my main
objections to my design was the addition of Shape::draw(Window*) because in
all the examples I have seen surrounding the Shape example, Shape only ever
has a pure virtual Shape::draw(), and that is why I felt my solution should
not be accepted. I often wondered if he mean't for the Window class to use
iterators supplied by each Shape object in order to get the Shape's shape;
that is, the character types, and position to be rendered on screen. As of
this writing, I have yet to read anything of his that had Shapes provide
iterators.

He uses examples like the following when illustrating proper design for this
example:
(src: http://technetcast.ddj.com/str0237/bs-millennium.pdf)

class Shape {
public:
virtual void draw() = 0;
virtual void rotate(double) = 0;
};

class Common { Color c; /* ... */ };

class Circle : public Shape, protected Common { /* ... */ };


As you can see, his Shape's draw() member does not take anything. This is
the difficult aspect causing me the concern with my design, as I feel that
there must be an elegant design that he had intended to be used. My adding
that Shape::draw() take a Window object was my method of solving 12.7[2],
while adhering to the requirement their be Window::draw(Shape&);

(Note, I mean't Shape& previously when writing Shape*).

I truly wish to understand proper OO design, and do not wish to continue my
study until I have understood what should be a very simple problem.

You can make your function parameters const if you want, but it's not usual
or necessary. Parameters are passed by value, so functions have their own
copies of them. It doesn't help the caller of a function know that a
function isn't going to modify its own copy of what's passed to it. The
caller doesn't care.

Your comment is interesting. In TC++PL, Mr. Stroustrup uses non-const ints
for parameters over const ints, but I thought he was doing that for some
reason that I had not learned about yet and so was using "const int"; that
is, using const on parameters passed by value. I did this because I wanted
to make sure in my function definitions that I would not modify the original
values that were passed. I understand that changing the values would not
affect the callers, but I did it so that my use of the variables would not
alter them. If that is not a good idea, please indicate why. I wish to do
such things correctly.
If you haven't done OO before and you are inexperienced in C++, I think
you're doing pretty well.

DW

Thank you very much for your feedback, as I found it most helpful! I am
glad a more experienced person thinks I am doing alright because I have not
talked to anyone about this and so had only my own opinion of myself. Not
having anyone to communicate with in regards to C++ can be difficult at
times.
 
A

Alf P. Steinbach

He uses examples like the following when illustrating proper design for this
example:
(src: http://technetcast.ddj.com/str0237/bs-millennium.pdf)

class Shape {
public:
virtual void draw() = 0;
virtual void rotate(double) = 0;
};

class Common { Color c; /* ... */ };

class Circle : public Shape, protected Common { /* ... */ };


As you can see, his Shape's draw() member does not take anything. This is
the difficult aspect causing me the concern with my design, as I feel that
there must be an elegant design that he had intended to be used.

Not very elegant, but there are various solutions _given_ the interface
above -- but I don't think Bjarne meant for that to be followed to the
letter, just in spirit.

The idea of passing a drawing surface object to the 'draw' function is
probably the most elegant and natural solution. I'd pass that by reference,
not as a pointer, because a pointer can be NULL. And I'd pass that as an
abstract interface, not as a concrete 'Window', but that's just details.

If you want to avoid an argument to 'draw' you can simply hook up your
object to the drawing surface (or window, if you want). That's not very
elegant, but works. E.g., pass the drawing surface object in to the 'Circle'
or 'Line' or whatever constructor, let each object keep a reference. Or
allow for more dynamic attachment and detachment. Although that's ugly and
fraught with dangers.


My adding
that Shape::draw() take a Window object was my method of solving 12.7[2],
while adhering to the requirement their be Window::draw(Shape&);

It would help if you posted the text of that exercise. Not all of us have
a recent edition of TCPPPL. Mine is from 1987, I think (I did have a more
recent edition, but someone borrowed it without my knowledge...).

That said, the best advice I can give is to trust your instincts. Yes,
Bjarne is God. And no, he's not above making a small mistake here and there
(if he were then there would be no errata list...). So don't get stuck on
following the letter of the text when you should be following the spirit of the
text. Or even -- following your own ideas and logic to wherever it leads.

Hth.
 
D

David White

Oplec said:
----- Original Message -----
From: "David White" <[email protected]>
Newsgroups: comp.lang.c++
Sent: Tuesday, October 21, 2003 8:40 PM
Subject: Re: How would Mr. Stroustrup implement his solution to 12.7[2]

I don't think this is such a good idea. Since the Shape object is doing the
drawing, there's no need for the Window class to know about Shapes. Just
pass a reference to the Window object to the Shape's draw(), and the
overridden function in the specific shape will draw itself on the
Window.

The Window class has to have a draw() member because such a member is
included in the example use of the final implementation of problem 12.7[2].
He shows that a Window, w, should be used as in the following:
w.draw(Circle(w.current(),10)); So, clearly, he intends for the solutions'
Window class to have a draw() member that takes a pointer to a Shape class.
I completely understand your idea, and would do it that way as well, but I
wish to complete this the way he intended it to be.

Fair enough. If that's what the exercise says...
One of my main
objections to my design was the addition of Shape::draw(Window*) because in
all the examples I have seen surrounding the Shape example, Shape only ever
has a pure virtual Shape::draw()

Yes, I've seen these as example OO designs as well, but they are usually
designs, not working implementations. I just don't think a draw() with no
parameters is right for a real graphics system. The Shape objects should be
independent of the device because it is not only possible, but highly
likely, that you will sometimes want to print whatever you've drawn on the
screen.
, and that is why I felt my solution should
not be accepted. I often wondered if he mean't for the Window class to use
iterators supplied by each Shape object in order to get the Shape's shape;
that is, the character types, and position to be rendered on screen.

Probably. I think the shapes are defined so that the n(), s() etc. members
are sufficient for the window to draw any shape. This is probably a good
enough design, or even the best design, for a system with these limitations.
Remember it is just an exercise and not necessarily a proposal for the best
design for graphics systems in general. For high resolution graphics systems
you will want to have dotted or dashed lines, thick or thin lines, curved
lines, ellipses, pear shapes, closed shapes with various kinds of shading
etc. The possibilities and complexities are endless, and in my opinion the
shape object will usually best know how to draw itself. The type of device
might make a difference too (e.g., raster or non-raster), but the shape
object is where most of the knowledge belongs. So, for this exercise
Stroustrup's design is sufficient, but for more complex drawing I don't
think it is.
As of
this writing, I have yet to read anything of his that had Shapes provide
iterators.

He uses examples like the following when illustrating proper design for this
example:
(src: http://technetcast.ddj.com/str0237/bs-millennium.pdf)

That file is not accessible to me at the moment.
class Shape {
public:
virtual void draw() = 0;
virtual void rotate(double) = 0;
};

class Common { Color c; /* ... */ };

class Circle : public Shape, protected Common { /* ... */ };


As you can see, his Shape's draw() member does not take anything. This is
the difficult aspect causing me the concern with my design, as I feel that
there must be an elegant design that he had intended to be used. My adding
that Shape::draw() take a Window object was my method of solving 12.7[2],
while adhering to the requirement their be Window::draw(Shape&);

I assure that, in general, there's nothing wrong with adding a device
parameter to the draw function. But it isn't necessary in this exercise,
since I think it's the Window that's doing the drawing.
(Note, I mean't Shape& previously when writing Shape*).

I truly wish to understand proper OO design, and do not wish to continue my
study until I have understood what should be a very simple problem.



Your comment is interesting. In TC++PL, Mr. Stroustrup uses non-const ints
for parameters over const ints, but I thought he was doing that for some
reason that I had not learned about yet and so was using "const int"; that
is, using const on parameters passed by value.

No, I think he's using non-const parameters because there's no need to make
them const and because it's the convention.
I did this because I wanted
to make sure in my function definitions that I would not modify the original
values that were passed. I understand that changing the values would not
affect the callers, but I did it so that my use of the variables would not
alter them.

Usually, programmers don't accidentally change the passed parameters. And
even if they do it won't affect anything outside the function. So, most
people don't bother making the parameters const.
If that is not a good idea, please indicate why. I wish to do
such things correctly.

My problem with it is mainly that the callers see the interface. For
example:

void f(const char * const p);

Here, the first const is important for the caller, but the second const is
only important for the function. I think it's better if function interfaces
are not cluttered with information not relevant to the caller. However,
others might disagree.

DW
 
J

jeffc

Oplec said:
Your comment is interesting. In TC++PL, Mr. Stroustrup uses non-const ints
for parameters over const ints, but I thought he was doing that for some
reason that I had not learned about yet and so was using "const int"; that
is, using const on parameters passed by value. I did this because I wanted
to make sure in my function definitions that I would not modify the original
values that were passed. I understand that changing the values would not
affect the callers, but I did it so that my use of the variables would not
alter them. If that is not a good idea, please indicate why. I wish to do
such things correctly.

Since you explain it that way, your technique is valid. Nonetheless,
programmers very rarely code this way. Some programmers have never seen it,
and for some reason it looks so odd to them that they might think it's a
bug, or at least go off in search of the answer. Therefore, from a
maintenance perspective, it might be considered "confusing". But a
programmer's misunderstanding of a properly working language feature doesn't
mean you shouldn't use it. There's nothing wrong with it. You might want
to make a comment as to your intent, if others read your code.
 
J

jeffc

David White said:
Here, the first const is important for the caller, but the second const is
only important for the function. I think it's better if function interfaces
are not cluttered with information not relevant to the caller. However,
others might disagree.

In theory, I definitely agree. In practice, there are certain things that
the language requires that force your hand. For example, when declaring
your function, you don't need to put the parameter names, just the types.
This is good - technically, it exposes a tiny part of the implementation.
(Of course, you might at least want to make a comment as to what the
parameter is for if it's not obvious.

void f(int, int); // fine
void f(int length) // fine - nice feature of C++
{...}

Unfortunately, you can't do this. This would also help hide some of the
implementation (you are using the const parameter as an implementation tool
only), but the language doesn't allow it.

void f(int);
void f(const int length)
{...}

So if you want to do this, it *has* to go in the declaration and the user
will have to see it.
 
J

jeffc

Oplec said:
Hi,

I am learning standard C++ as a hobby. The C++ Programming Language :
Special Edition has been the principal source for my information. I read the
entirety of the book and concluded that I had done myself a disservice by
having not attempted and completed the exercises. I intend to rectify that.

My current routine is to read a chapter and then attempt every exercise
problem, and I find that this process is leading to a greater understanding
of the language and implementation.

However, occasionly I am unable to complete some of the exercises, or am not
satisfied with my method of solution. One such exercise is 12.7[2].

From what I read in the chapter and a few papers of his, Mr. Stroustrup
suggests that most of the time the best implemention for the Shape, Dot,
Line, Rectangle hierarchy would be for Shape to be a purely abstract base
class, and for Dot, Line, Rectangle to inheirit from Shape and implement the
virtual functions. In his code examples he routinely defines "virtual void
draw() = 0" as a member of the Shape class. Upon implementing a solution to
12.7[2] I experienced a difficulty surrounding how to implement the drawing
of Shapes for the Window class that must exist. My implementation was for
simple console mode character output using std::cout.

Since I am new to OO programming, I feel that I lack some understanding of
what he intended my solution to be similar to.

If you still have questions, why don't you ask him personally? You would
want to distill the message down to be as succinct as possible because I'm
sure he's very busy. He has helped me when I couldn't find a complete
answer here, and in your case you're asking something directly related to
his book.
 
O

Oplec

It would help if you posted the text of that exercise. Not all of us have
a recent edition of TCPPPL. Mine is from 1987, I think (I did have a more
recent edition, but someone borrowed it without my knowledge...).

Hello,

I would copy 12.7[2] from the book and reproduce it here, but all such
rights are reserved by the publisher. However, I will do my best to
represent the idea.

Write a simple graphics system, and if you don't have access to a proper
graphics system you may use an ASCII implementation. In that implementation,
points are represented by a character position and you write a suitable
character to the screen. A Window class has a constructor Window(i,j), where
i is the width, and j the height, of an area on the screen. Points use
Cartesian coordinates. A Point is represented as a coordinate pair,
Point(x,y), but a Point is not a Shape. Window has the member
Window::current() that returns the current position. Default position for
Window is Point(0,0). w.current(p) can be used to set the current position,
where p is a Point. A line is represented by two Points, for example
Line(w.current(),p7); There is a Shape class that is the base interface for
all drawable shapes such as Lines, Dots, Rectangles, Circles. The Dot shape
is used to represent a Point that is to be drawn. All Shapes are invisible
until they are draw()n. Usage: w.draw(Line(w.current(), 20)); All the shapes
have 9 points of contact: n (north), e(east), s (south), w (west), ne
(north-east), nw (north-west), se (south-east), sw (south-west), and c
(center). After a shape is drawn, w.current() equals the shape's se().
Usage: Line(x.s(), y.c());, which will create a Line from x's south point to
y's center point. A Rectangle is created Rectangle(p1, p2), where p1 is the
rectangle's bottom left point, and p2 is its top right. Usage:
Rectangle(Point(20,20), w.current()); To demonstrate the working of your
solution, render a child's drawing of a house, that is, a roof, two windows,
the body, and a door.

The following is my understanding of the previous paragraph:
What that all means is that there should ast least be an abstract base class
named Shape that contains the virtual members n(), e(), s(), w(), ne(),
nw(), se(), sw(), c(). He did not specify a Shape::draw(), but he used such
a member in an example in the book.

The Point class simply holds Cartesian coordinates.

A Dot derived from Shape and is simply a Shape having a single Point and
represented as a single character position on the screen.

The Line is derived from Shape and is created by supplying two Points, and
drawn as a series of characters between and including those positions. I had
to work out a method to actually draw lines, and then discovered that there
is a Bressenham algorithm for doing this. It would have saved me much paper
figuring it out myself.

The Window class is created by supplying two coordinates, but I do not
believe those to be Points, but rather ints. I could be mistaken. It has a
current() member that returns the current Point, that is the last shape's
se() or Point(0,0). A current(Point) member that sets the current position.
And a draw(Shape&) that draws a shape, and is at the heart of my objection,
concern over my implementation.

That constitutes what I know for certain of the problem. My translation of
12.7[2] is in keeping with its idea while not violating the copyright.

Please let me know how you would solve this problem, knowing that I can only
use the ASCII reprentation for the graphics. Any ideas are most welcome.

Thank you for time, Oplec.
 
O

Oplec

Yes, I've seen these as example OO designs as well, but they are usually
designs, not working implementations. I just don't think a draw() with no
parameters is right for a real graphics system. The Shape objects should be
independent of the device because it is not only possible, but highly
likely, that you will sometimes want to print whatever you've drawn on the
screen.

If I had were to have free reign over the design, I would most likely
implement a Surface class that Shapes draw to. My only question would then
be whether or not such a Surface should provide the members for drawing to
it, such as put_pixel(), or whether another class should be implemented to
do so. Any ideas?
Probably. I think the shapes are defined so that the n(), s() etc. members
are sufficient for the window to draw any shape. This is probably a good
enough design, or even the best design, for a system with these limitations.
Remember it is just an exercise and not necessarily a proposal for the best
design for graphics systems in general. For high resolution graphics systems
you will want to have dotted or dashed lines, thick or thin lines, curved
lines, ellipses, pear shapes, closed shapes with various kinds of shading
etc. The possibilities and complexities are endless, and in my opinion the
shape object will usually best know how to draw itself. The type of device
might make a difference too (e.g., raster or non-raster), but the shape
object is where most of the knowledge belongs. So, for this exercise
Stroustrup's design is sufficient, but for more complex drawing I don't
think it is.

From my understanding of the problem, and how I implemented it, the
Points returned by the n(), e(), ..., members could not be used to draw a
shape unless Window knew of what shape the object being drawn was, such as
Line. That would violate the whole idea of what Mr. Stroustrup wrote in the
chapter; He seems to detest casts.
No, I think he's using non-const parameters because there's no need to make
them const and because it's the convention.

If it is the convention then I will follow it. Thank you for informing
me of this matter since I was not expecting any help on my code, but it is
most welcome.

Thank you for your time, Oplec.
 
O

Oplec

Since you explain it that way, your technique is valid. Nonetheless,
programmers very rarely code this way. Some programmers have never seen it,
and for some reason it looks so odd to them that they might think it's a
bug, or at least go off in search of the answer. Therefore, from a
maintenance perspective, it might be considered "confusing". But a
programmer's misunderstanding of a properly working language feature doesn't
mean you shouldn't use it. There's nothing wrong with it. You might want
to make a comment as to your intent, if others read your code.

Mr. White explained that it is the convention to not do so, and so I
will go along with such a convention because I am new to C++ and there is
probably a excellent reason for its justification. My using "const" on
parameters passed by value was to assist me when programming the definitions
of functions. When I first began, my functions were often very long and
confusing and having the parameters be "const" was a method I adopted to
combat erroneously changing the parameters well into my definitions. When I
was in the process of solving an exercise, where I was to write a sort
routine for my own doubly linked-list, I ran into much difficulty as a
result of accidentally modifing the passed pointer parameters. I wrote many
versions of the sort algorithm because I had to think about a way to sort
things myself and implement it. When implementing it I soon found out that I
had not fully memorized the operator precedence levels and left-right
associativity rules. I first started using "const" on pointers and
references. I soon, thereafter, used it on value types because I encountered
similar difficultys with accidentally modifying arguments.

I'll comply with convention.

Thanks for your time, Oplec.
 
O

Oplec

If you still have questions, why don't you ask him personally? You would
want to distill the message down to be as succinct as possible because I'm
sure he's very busy. He has helped me when I couldn't find a complete
answer here, and in your case you're asking something directly related to
his book.

Quite oftern I visit his website and read his papers. He has a FAQ that
states that there is a good chance that homework problems would not get
answered, and that is why I had not attempted to contact him through e-mail.
I assumed that he would believe my attempt at contact to be solely to obtain
an solution to a problem that I had not solved. Seeing as I did solve it, I
could, perhaps, be quick to indicate that I had solved it. That might
increase my chances of a response.

In actuality, I will probably not contact him because I respect him too much
to waste his time on what is a trivial matter to him.

Thanks for your time, Oplec.
 
L

lilburne

Oplec said:
I first started using "const" on pointers and
references. I soon, thereafter, used it on value types because I encountered
similar difficultys with accidentally modifying arguments.

I'll comply with convention.

I have to say that f(const int) tends to jar my
sensibilities, but some colleagues use the idiom for the
same reasons that you have expressed.

Personally I've never quite understood why it is that if
they are concerned about not modifying the argument they
don't simply refrain from doing so and assert just before
the function returns that the argument retains its original
value.
 
L

lilburne

David said:
Yes, I've seen these as example OO designs as well, but they are usually
designs, not working implementations. I just don't think a draw() with no
parameters is right for a real graphics system. The Shape objects should be
independent of the device because it is not only possible, but highly
likely, that you will sometimes want to print whatever you've drawn on the
screen.

We separate drawing into other objects. For example a Line,
Circle, or Bezier curve, are geometrical objects that have
mathematical properties which have nothing to do with
drawing. They can be used in applications that do not
involve drawing at all. Applications that need to draw
geometrical objects (GO) attach the GOs to geometrical
drawing objects.
 
D

David White

lilburne said:
We separate drawing into other objects. For example a Line,
Circle, or Bezier curve, are geometrical objects that have
mathematical properties which have nothing to do with
drawing. They can be used in applications that do not
involve drawing at all. Applications that need to draw
geometrical objects (GO) attach the GOs to geometrical
drawing objects.

I was referring only to drawable objects. I wasn't suggesting that a Shape
hierarchy intended for mathematical use should know how to draw.

DW
 
L

lilburne

David said:
I was referring only to drawable objects. I wasn't suggesting that a Shape
hierarchy intended for mathematical use should know how to draw.

I wasn't criticizing, just presenting another way of
arranging this sort of thing. The geometry example was just
that and example. None of our domain objects, whether they
be curves, surfaces, machines, or material, 'know' about
drawing.
 
D

David White

Oplec said:
If I had were to have free reign over the design, I would most likely
implement a Surface class that Shapes draw to. My only question would then
be whether or not such a Surface should provide the members for drawing to
it, such as put_pixel(), or whether another class should be implemented to
do so. Any ideas?

I think the Surface class would have basic low-level drawing functions. I
have designed and implemented a graphics system for Windows before. A used a
Shape hierarchy like this (though I called the base class GraphObject), and
my draw function took a GraphContext object. The GraphContext object did
resource management for the graphical objects (selected/deselected pens,
brushes etc.) and was a wrapper for a Windows device context (or CDC
object). (I didn't want my drawable objects to be Windows-specific.) The
drawable objects drew themselves on the GraphContext using the sort of basic
drawing operations that the device context has (draw line, for example). The
GraphContext then drew on the CDC object itself. To draw on a different
surface you simply pass to the draw function a GraphContext that was a
wrapper for a different DC object (printer DC etc.).
From my understanding of the problem, and how I implemented it, the
Points returned by the n(), e(), ..., members could not be used to draw a
shape unless Window knew of what shape the object being drawn was, such as
Line. That would violate the whole idea of what Mr. Stroustrup wrote in the
chapter; He seems to detest casts.

I've read the exercise myself (in the 3rd edition) and it's not clear to me
what you are supposed to do. I don't understand what object is supposed to
do the drawing, and I don't understand what the attributes of the Shape
objects are. It says that a Line consists of two Points. But further down it
says that a Line is also a Shape, and that a Shape has 9 "contact" points
(whatever that means): e (east), w (west), c(center) etc. And take this
passage: "For example, Line(x.c(), y.nw()) creates a line from x's centre to
y's top left corner." What are x and y here? Are they other Shape objects? I
don't understand the exercise at all.

DW
 
A

Alf P. Steinbach

I've read the exercise myself (in the 3rd edition) and it's not clear to me
what you are supposed to do. I don't understand what object is supposed to
do the drawing, and I don't understand what the attributes of the Shape
objects are. It says that a Line consists of two Points. But further down it
says that a Line is also a Shape, and that a Shape has 9 "contact" points
(whatever that means): e (east), w (west), c(center) etc. And take this
passage: "For example, Line(x.c(), y.nw()) creates a line from x's centre to
y's top left corner." What are x and y here? Are they other Shape objects? I
don't understand the exercise at all.

I haven't read the exercise but it seems very clear what that's all about.
A line is defined by its two endpoints. It has an implicit bounding rectangle
with nine contact points: the corners of the rectangle, the rectangle side
midpoints, and the rectangle centre.

What's a bit confusing is what e.g. the centre of an arc should be.

But I think it would just be wise to distinguish between shape centre and
line midpoint, even though they're the same for a straight line.
 
D

David White

Oplec said:
down centre objects?

He means the following:
Point Shape::nw() const;
Point Shape::c() const;
Line::Line(Point, Point);

Yes, I got this much, but the exercise still escapes me. I'm glad you and
A.P.S. understand it, though.

I'm also confused over draw(). As you pointed out in your first post,
elsewhere in the book his Shape classes have a draw() member, but this
exercise has a Window::draw and does not mention Shape::draw.
I believe that I understand the problem, but I'm not satisfied with my
solution. My programs seems fine and correctly outputs the simple child's
drawing of a house. When the program is run, you can see the drawing in
ASCII. It uses only Standard C++.

I read that I can attach files that are under 1 MB in size, and so I am
attaching my solution to the problem to this post. The program may be
compiled and you will see the drawing of the house and how it conforms with
what Mr. Stroustrup set as criteria. However, I am not satisfied that I
found the particular solution he would write himself under such
constraints.

As I don't even understand the exercise, I don't think I can comment any
more than I have.

BTW, attachments don't really bother me, but in general they don't go down
well in non-binary newsgroups. Most people prefer that you just include the
code in your post.

DW
 
J

jeffc

lilburne said:
I have to say that f(const int) tends to jar my
sensibilities, but some colleagues use the idiom for the
same reasons that you have expressed.

Personally I've never quite understood why it is that if
they are concerned about not modifying the argument they
don't simply refrain from doing so and assert just before
the function returns that the argument retains its original
value.

A lot of people tend to agree with you, but I really don't understand this
view, as it seems inconsistent to me. Of all the crazy things you can (and
can not) do in C++, programmers pick out this simple one to "not get".
Regarding your comment "simply refrain from doing so" - well, why ever use
const? Why not simply refrain from not changing things that shouldn't be
changed? For that matter, why not simply refrain from writing code with any
bugs in it?
 

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,578
Members
45,052
Latest member
LucyCarper

Latest Threads

Top