October 90 - POLYMORPHIC CODE RESOURCES IN C++
POLYMORPHIC CODE RESOURCES IN C++
PATRICK C. BEARD
The C++ programming language supports data abstraction and object programming.
Until now, using C++ to its full capacity in stand-alone code has not been possible.
This article demonstrates how you can take advantage of two important features of
C++, inheritance and polymorphism, in stand-alone code. An example shows how to
write a window definition function using polymorphism.
In object programming, polymorphism gives programmers a way to solve problems by
beginning with the general and proceeding to the specific. This process is similar to
top-down programming, in which the programmer writes the skeleton of a program to
establish the overall structure and then fills in the details later. Polymorphism
differs from top-down programming, however, in that it produces designs that are
reusable outside the context of the original structure. The attractiveness of reusable
code is one of the reasons object programming is catching on.
The shape hierarchy shown in Figure 1 is one of the most frequently cited examples of
polymorphism.
Figure 1Shape Hierarchy
The most general concept is the shape; all objects in the hierarchy inherit attributes
from the shape. Area, perimeter, centroid, and color are attributes common to all
shapes. Notice that the hierarchy proceeds from the general to the specific:
• Polygons are shapes with a discrete number of sides.
• Rectangles are polygons that have four sides and all right angles; squares
are rectangles having all equal sides.
• Ellipses are shapes that have a certain mathematical description; circles
are ellipses whose widths equal their heights.
In C++, concepts are represented as classes. The more abstract the concept, the higher
in the inheritance hierarchy the concept resides. Two key C++ features support
polymorphism: inheritance and virtual member functions. We can use these to develop
more concretely specified shapes and ask questions of any shape about its area,
perimeter, or centroid.
The virtual functions provide a protocol for working with shapes. Here is an example
of the shape hierarchy as it could be represented in C++:
virtual float area(); // Area of the shape.
virtual float perimeter(); // Its perimeter.
virtual Point centroid(); // Its centroid.
class Ellipse : public Shape {
public:
virtual float area(); // Area of the shape.
virtual float perimeter(); // Its perimeter.
virtual Point centroid(); // Its centroid.
private:
Point center; // Center of ellipse.
float height; // How high.
float width; // How wide.
class Circle : public Ellipse {
public:
virtual float area(); // Area of the shape.
virtual float perimeter(); // Its perimeter.
virtual Point centroid(); // Its centroid.
In this implementation, a circle is an ellipse with the additional constraint that its
width and height must be equal.
Once an object of a type derived from Shape has been instantiated, it can be
manipulated with general code that knows only about shapes. The benefit is that, having
written and debugged this general code, you can add more kinds of shapes without
having to alter the general code. This eliminates many potential errors.
IMPLEMENTATION IN MPW C++
MPW C++ is a language translator that translates C++ to C. Programs are compiled by
first being translated to C, after which the MPW C compiler takes over and compiles
the C to object code.
IMPLEMENTATION OF VIRTUAL FUNCTIONS
As noted, polymorphism is accomplished by using inheritance and virtual member
functions. How does the C++ compiler decide which function should be called when an
instance of unknown type is used? In the current release of MPW C++, every instance
of an object in an inheritance hierarchy has a hidden data member, which is a pointer
to a virtual function table. Each member function is known to be at a particular offset
in the table. The member functions for the different classes in an inheritance chain are
stored in different tables. The table pointed to is determined at the time of object
creation. (See the sidebar called "Layout of Objects and Their Virtual Functions in
Memory.")So far, nothing in the implementation of virtual functions seems to
preclude their use in nonapplication contexts. Once an object is instantiated, the code
needed to call a virtual function can be executed from any context, including
stand-alone code resources. However, MPW C++ does not currently support a
mechanism to allocate storage for, or to initialize, the virtual function tables in
nonapplication contexts.
CODE RESOURCE SUPPORT FOR POLYMORPHISM
As noted above, virtual function tables are required for polymorphism in C++. To
support virtual function tables in stand-alone code, two issues must be resolved:
• How to allocate the virtual function tables.
• How to initialize the virtual function tables.
GLOBAL VARIABLES IN CODE RESOURCES
In MPW C++, virtual function tables live in C global variable space. Unfortunately,
the MPW languages do not support the use of global variables in stand-alone code.
However, Technical Note #256, Stand- Alone Code,ad nauseam , shows how to add
support for global variables in standalone code resources. In simple terms, this
involves allocating storage for the globals, initializing the globals, and arranging for
the proper value to be placed in machine register A5. These functions can be neatly
expressed as a class in C++. The following class, called A5World, provides these
services.
A5World(); // Constructor sets up world.
~A5World(); // Destructor destroys it.
// Main functions: Enter(), Leave().
void Enter(); // Go into our world.
void Leave(); // Restore old A5 context.
// Error reporting.
OSErr Error() { return error; }
private:
OSErr error; // The last error that occurred.
long worldSize; // How big our globals are.
Ptr ourA5; // The storage for the globals.
Ptr oldA5; // Old A5.
To use globals, a code resource written in C++ merely creates an instance of an
A5World object. Here is an example:
// Hello_A5World.cp
// Simple code resource that uses global variables.
// Array of characters in a global.
char global_string[256];
void main()
// Temporarily create global space.
A5World ourWorld;
// Check for errors.
if(ourWorld.Error() != noErr)
return;
// We got it; let's go inside our global space.
ourWorld.Enter();
// Use our global variable.
strcpy(global_string, "Hi there!");
debugstr(global_string);
// Time to go home now.
ourWorld.Leave();
// The destructor automatically deallocates
// our global space.
}
The full implementation of class A5World appears on the Developer Essentials disc
(Poly. in Code Resources folder). By itself, this is a useful piece of code.
INITIALIZING THE VIRTUAL FUNCTION TABLES
As noted, MPW C++ is implemented as a language translator (called CFront) that
translates C++ to C. As you might guess, classes are implemented as structs in C, and
member functions are just ordinary C functions. As also noted, the virtual function
tables are implemented as global variables. We have solved the problem of having
globals in stand-alone code, so the remaining issue is how to initialize these tables
with the proper pointers to the member functions.
The initialization of a global variable with a pointer to a function is not supported in
stand-alone code written in MPW languages. This initialization is normally done by the
linker, which creates a jump table, and the current version of the MPW Linker will
not generate jump tables for stand- alone code. Therefore, the only way to initialize
global variables with pointers to code is manually at run time.
To understand the solution to this problem, let's take a look at what CFront does when
it sees a hierarchy of classes with virtual functions. Here is a simple hierarchy of two
class Base {
public:
Base();
virtual void Method();
class Derived : public Base {
public:
Derived();
virtual void Method();
When MPW C++ sees these class definitions, it emits the following C to allocate and
initialize the virtual function tables:
struct __mptr __vtbl__7Derived[]={0,0,0,
0,0,(__vptp)Method__7DerivedFv,0,0,0};
struct __mptr *__ptbl__7Derived=__vtbl__7Derived;
struct __mptr __vtbl__4Base[]={0,0,0,
0,0,(__vptp)Method__4BaseFv,0,0,0};
struct __mptr *__ptbl__4Base=__vtbl__4Base;
The variables __vtbl__4Base[] and __vtbl__7Derived[] are the virtual function