Re: Help a poor FORTRAN programmer with member functions?

From: Kevin Goodsell (usenet2.spamfree.fusion_at_neverbox.com)
Date: 04/08/04


Date: Thu, 08 Apr 2004 19:57:54 GMT

Anthony Jones wrote:

> Just a bit of background: I'm one of a group of FORTRAN programmers, looking
> to switch to C++. We are trying to write a few simple examples to
> demonstrate the power of the language to our manager, so he will send us all
> on a conversion course.
>
> One of many reasons is that our code is littered with examples of:
>
> SUBROUTINE PRINT_ITEM(ITEM, ITEM_TYPE)
> IF (ITEM_TYPE .EQ. SQUARE) THEN
> CALL PRINT_SQUARE(ITEM)
> ELSEIF (ITEM_TYPE .EQ. CIRCLE) THEN
> CALL PRINT_CIRCLE(ITEM)
> ELSEIF ...
> (lots more item types)
> ENDIF
> END

Yikes.

>
> (with apologies for sullying the group with FORTRAN code).
>
> Obviously We need to find and modify all these blocks whenever we add a new
> object type, or operation.
>
> I want to write a C++ equivalent, using classes and member functions so that
> I can print (or draw, or interrogate, or whatever...) an object without
> knowing its type at runtime.
>
> The latest of several attempts is shown below - the compiler complains about
> the void* in the PrintObject function, though I thought I'd read that void*
> could be used to mean "pointer to something, but I don't know what".

Yes, and it also means "no type safety". void* should be avoided, and is
certainly not the right way to accomplish polymorphic behavior (which is
what you are looking for, whether you know that or not).

>
> Can this code be modifed to get the effect I want? I'd like to avoid using
> pointers to functions if possible.
>
> Thanks!
>
>
> #include <iostream.h>

I realize you are new to this, so please understand that I'll point out
any and all errors I spot in your code, regardless of whether they are
relevant to the immediate question. Also, my definition of an 'error'
includes anything that is not defined by the C++ standard, anything that
may behave unexpectedly, or that may behave differently on different
implementations (even if one of the "different implementations" is a
hypothetical implementation that does not actually exist). This is
standard practice for many of the people in this group.

That said, <iostream.h> is not part of the C++ standard. It is old,
pre-standard C++. Standard C++ uses <iostream> (with no .h).

>
> // Class declarations
> // ------------------
>
> class Square{
> public:
> void Print();
> };

In order to achieve polymorphic behavior, you need a few things that you
are missing: Inheritance, and virtual functions. Your example involves
shapes. Great, so create a class representing a shape. It will serve as
the base class for you other shapes.

class Shape
{
public:
   virtual void Print() = 0;
};

class Square : public Shape
{
public:
   void Print();
};

class Circle : public Shape
{
public:
   void Print();
};

The "class Square : public Shape" part can be read as "Square IS A
Shape". This is often referred to as the 'is-a' relationship. Squares
and Circles are both types of Shapes, and thus share some of the same
functionality -- in particular, they all have the ability to perform the
'Print' operation.

The 'virtual' qualifier on Shape::Print() just means "this function can
behave differently in different base classes". This allows Square and
Circle to give their own version of Print() that does what they need it
to do. The '= 0' part is a bit more confusing. It has basically 2
effects: 1) It allows Shape to decline to implement Print(). There's no
reasonable way to print a generic shape, so this makes sense. It
essentially requires base classes to provide Print() instead (though a
base class can pull the same trick, passing the burden of implementing
the function onto /its/ base classes). 2) It makes Shape an abstract
class, which is a class that can really only be used as a base class.
You can't create an object of type Shape, because Shape is not a
complete class.

>
> void Square::Print(){
> cout << "This is a square";

In modern C++, 'cout' (along with most standard library names) resides
in namespace std. This means that the fully qualified name is std::cout.
You can use this fully-qualified name, or you can put a line like this:

using namespace std;

at the top of your source files, just after the #include directives.
This is sort of the lazy way of doing it, and can be bad in some cases,
but it's the easiest way to get started. There are other options as
well, but you'll learn about that later.

Other than that, no changes are required here.

> }
>
> class Circle{
> public:
> void Print();
> };
>
> void Circle::Print(){
> cout << "This is a circle";

Same comments as for Square::Print().

> }
>
> // Print object function
> // ---------------------
>
> void PrintObject(void* object){

Now, you don't want to use void* here. What you want is a function to
print an object. More specifically, a Shape. So try this instead:

void PrintShape(Shape *shape)
{

> object->Print();

shape->Print();

> }
>
> // Main Program
> // ------------
>
> int main(){
> Square* square;
> Circle* circle;
>
> square = new Square;
> circle = new Circle;

Generally, you shouldn't use 'new' unless you absolutely have to. It
tends to be used in real programs that do what you are demonstrating, so
its use here isn't completely inappropriate, but normally when you want
a Square you should just say

Square my_square;

It's also worth noting that a more typical way of doing what you are
doing would be like this:

Shape *square;
Shape *circle;

square = new Square;
circle = new Circle;

In fact, you'd probably be most likely to have a collection of Shape
pointers (in an array, or a container class). Such collections generally
have to be homogeneous, so they can't contain Square pointers and Circle
pointers. Luckily they don't need to, because Shape pointers can point
to Squares, Circles, and any other type that IS A Shape.

>
> // Call member functions directly
>
> circle->Print();
> square->Print();
>
> // Call member functions through PrintObject function
>
> PrintObject(circle);
> PrintObject(square);

Replace these with the new name 'PrintShape' and you're all set.

>
> return 0;
>
> }
>

-Kevin

-- 
My email address is valid, but changes periodically.
To contact me please use the address from a recent posting.


Relevant Pages

  • Help a poor FORTRAN programmer with member functions?
    ... I'm one of a group of FORTRAN programmers, ... SQUARE) THEN ... CIRCLE) THEN ... the void* in the PrintObject function, though I thought I'd read that void* ...
    (comp.lang.cpp)
  • Re: Can I make something circular?
    ... Everything in Publisher is square. ... Keep your design inside the circle, ... I have used Neato labels and program. ... I've been looking into printing labels directly on discs (my Canon pixma ...
    (microsoft.public.publisher)
  • Re: Any one know of a circle cutting jig for bandsaw, or table saw?
    ... Cut a square from your stock on the table saw so that its side is ... slightly longer than the diameter of your circle. ... that fits snugly in the hole but turns freely, screw the square to ... Raise the blade and cut the corners off the square by running ...
    (rec.woodworking)
  • Object-Oriented Programming (run-time polymorphism) with C99
    ... Shape is the base class and Circle is derived from Shape. ... // destructors void Point_destroy; ... unsigned int G; // green ... // representation const ...
    (comp.lang.c)
  • Re: Connecting semi-circles
    ... 'squaring the circle', or its inverse 'circling the square' ... continuous surface area and then moving it to a square with a perfect, ... There is no way to get a 'smooth corner.' ... Is it possible to create two semicircles so that: ...
    (sci.math)