June 91 - The Soup Kitchen - C++ing with MacApp
The Soup Kitchen - C++ing with MacApp
Eric M. Berdahl
ETO #4 will soon arrive on our doorsteps, if you believe the current APDA release
schedule. An early version of MacApp 3.0 is promised on that CD-the so-called "C++
MacApp." This means, among other things, that many developers will need to be able to
read C++ code, and perhaps even need to patch it. This issue, I'll look at some common
MacApp constructs and show how C++ handles them.
ALLOCATION AND DEALLOCATION
One of the basic concepts of object programming is creating objects and disposing of
them. These topics, in turn, break down into two separate issues: allocation and
deallocation, and construction and destruction. MacApp supports all these areas,
sometimes by convention, and other times by collaboration with the development
environment.
Object Pascal extends Pascal's allocation and deallocation routines, NEW and DISPOSE,
to work on object variables. Thus, the following may be written in Pascal:
VAR
anObject: TObject;
BEGIN
NEW(anObject);
DISPOSE(anObject);
END;
The Pascal compiler translates calls to NEW with an object argument into calls to a
"magic" subroutine %_OBNEW, which then calls routines to allocate storage. In C++,
classes that inherit from PascalObject also get this behavior. Thus, the equivalent C++
code looks like this:
{ TObject* anObject;
anObject = new TObject;
delete anObject;
}
So, judging from this section of code, the new and delete functions are C++ analogs of
NEW and DISPOSE. However, you have to tell new to create a TObject, whereas NEW
simply knows what to create. When the Pascal compiler sees a call to the NEW
procedure, it looks at the argument and decides what type it is. If the variable is an
object, it calls the aforementioned %_OBNEW procedure, passing it information about
the class of the NEW argument.
C++ takes a slightly different approach. The C++ new is told what type to create-
TObject in this example-and calls %_OBNEW with the information about the class
indicated by the programmer. The new function then returns a reference to the indicated
class. This means that you can assign the result of new to a variable of that class or,
alternatively, to a variable of a parent class. Thus, the code in the DoMenuCommand in
C++ listing is perfectly legal. The equivalent Pascal code is shown in the
DoMenuCommand in Pascal listing. And, of course, the C++ code may also be written
using explicit variables for TFooCommand and TBarCommand, if you prefer.
In contrast, delete works on Pascal objects just as the DISPOSE routine, although in
the MacApp world the Free method is always used instead.
CONSTRUCTION AND DESTRUCTION
Walking hand-in-hand with allocation and deallocation are the ideas of construction and
destruction. Construction is the concept that an object should be placed into a known state
as soon as it's created. Destruction is the concept of disposing of "owned" objects or
performing other clean up necessary when an object ceases to exist. C++ provides a
language mechanism that guarantees that these things occur when and where appropriate.
Thus, there is a syntax for defining a constructor for a class that is called when an
instance begins its existence, and a destructor that is called when an instance ceases to
exist.
The implementation of these features in the language is very robust; they ensure that
the parent class is completely constructed before the child class and that the child is
destructed before the parent.
Constructors and destructors are common in pure C++ code; however, you should
never write constructors for classes inheriting from PascalObject (i.e. classes meant to
be link-compatible with Object Pascal). In the MacApp world, initialization methods are
always used instead. MacApp 2.0.x "IMyObject" initialization methods have the general
form:
BEGIN
SetMyInstanceVariablesToSafeValues;
SELF.IMyParentObject;
InitializeMyInstanceVariablesToRealValues;
END;
The SetMyInstanceVariablesToSafeValues part acts like a constructor. The purpose is to
place the object into a state such that Free is safe to call, if necessary. This means that
pointers are set to nil, and so on. Some time ago, there was some discussion on
MacApp.Tech$ contending that the TObject method Initialize should automagically be
called when an object is allocated; this would add a more automatic construction behavior
to MacApp.
A sort of automatic construction behavior is present in the MacApp 3.0 world. MacApp
3.0 IMyObject methods simply move the constructor portion shown above into an
Initialize method. Initialize is invoked from IObject, and is implemented for all the
standard MacApp classes. All subclasses define overrides of Initialize which first call the
inherited version, then initialize local instance variables to safe values. Thus, the only
time it is dangerous to Free an object is between allocation (i.e. new) and initialization
(i.e. IMyObject). In practice this is not a problem if you follow the convention that
Initialize must not fail. Following this convention should not be difficult since Initialize
should only set instance variables to default values, and nothing more.
Destruction is handled by Free methods. By convention, a class' Free method does all
necessary clean-up before invoking the parent class' version of Free. This does by
convention what C++ destructors do automagically. So, if C++ constructors and
destructors are so great, why not use them in MacApp? Because that would result in code
that isn't link-compatible with Object Pascal. Pascal won't call C++'s constructors or
destructors, so relying on them could lead to disastrous consequences.
A SIMPLE ROUTINE
The GetQDExtent in C++ listing shows an actual method taken from the MacApp 3.0
source code. I'll refer to it several times to denote various constructs used in C++
coding. The GetQDExtent in Pascal listing shows the equivalent Pascal code.
One of the first things to notice is that the C++ version of GetQDExtent uses the pascal
void construct. Remember from the introduction to C++ interfaces in the last issue that
pascal <Something> denotes a Pascal FUNCTION with return type <Something>, and that
pascal void denotes a Pascal PROCEDURE.
Next, notice that "TView::GetQDExtent" correlates with "TView.GetQDExtent" in the
Pascal code. The "::" is called the scope resolution operator. It casts a fair amount of
magic in purist C++ code, but only has two common uses in the MacApp world. Method
declaration as seen here is one place where "::" is used; the other will be revealed
shortly.
Further comparison shows that the C++ keyword this is equivalent to the Pascal
keyword SELF. Just as SELF is a Pascal meta-variable that indicates the particular
instance a method is manipulating, this is the C++ meta-variable. All magic provided by
Pascal in regards to SELF carries over to this in C++.
ACCESSING CLASS FEATURES
Method invocations and instance variable access are produced with the arrow operator,
"->". Like its Pascal cousin, the dot operator, ".", the C++ arrow operator works on an
object to call a method or access an instance variable. So, this->GetExtent(vr) is the
C++ equivalent of the Pascal SELF.GetExtent(vr).
Similarly, you use anObject->fAnInstanceVar in a C++ method to do something with
the instance variable fAnInstanceVar or the anObject object. Although this-
>fAnInstanceVar is syntactically correct, you can just write fAnInstanceVar. The this->
is implied in C++ methods just as SELF. is implied in Pascal methods, and the convention
of beginning field names with lowercase f makes it clear that fAnInstanceVar is an
instance variable. For clarity, however, most style guides recommend explicitly using
this->MethodCall() in C++ just as one would use SELF.MethodCall in Pascal.
There is another form of method invocation common to MacApp programming: calling
the parent class' version of a method. Object Pascal provides the INHERITED keyword for
this purpose. For a discussion of this topic, see James Plamondon's article
"TAspectPicture-A problem to sleep on" in the April '91 issue of FrameWorks.
In C++, you can call the parent class' version of a method in two different ways.
Traditional C++ programmers use the construct TParentClass::MethodCall(arg). In this
form, MethodCall names the method you want to invoke, and TParentClass is the class
that implements the version of MethodCall you want to use (passing arg as an argument).
This construct bypasses the method dispatcher and explicitly calls the indicated
implementation of MethodCall. This construct allows you to skip up the inheritance chain
directly to any class that implements the method you name-parent class, grandparent
class, etc-without executing implementations of that method that are made by
intervening classes in the inheritance chain.
Usually you don't want to bypass the method dispatcher in this fashion. Instead, you
want to dispatch your method starting with the parent class. Due to what I feel is a deficit
in C++, no shorthand exists for calling an inherited method in this manner; however,
MPW C++ provides an extension to do just that based on Pascal's INHERITED keyword. In
MPW C++ you can write inherited::Draw(aRect) just as you might write INHERITED
Draw(aRect) in Pascal.
VARIABLE DECLARATIONS
One major difference between C++ and Pascal methods is their local variable
declarations. Pascal provides an explicit VAR area for all variable declarations. In C++,
a variable declaration (e.g. "char aChar;") is a full-fledged statement; thus, it can
appear anywhere a statement may appear in code.
Arbitrary placement of local variable declaractions is another feature of the language
that may be important if you do pure, non-MacApp C++ coding; however, as a matter of
style, many Mac C++ programmers declare all local variables in a cluster at the
beginning of a method. A common variant on the simple declaration is the addition of an
initial value to the declaration. Thus, "char aChar = 'a';" not only declares a variable
named aChar of type char, it also immediately sets it to be the character 'a'.
REFERENCE VARIABLES
I'm going to take a break from dissecting this method to discuss C++ reference
variables. These are possibly the most difficult concept of C++ to grasp, because they
have no correlation in Pascal. A reference variable looks like this:
short anInteger;
short& someInteger = anInteger;
Here, anInteger is an integer, and someInteger is a reference to an integer variable
(anInteger in this case).
References can be thought of as pointers that must always point to something. Another
popular analogy is that references are aliases to another variable. Since references must
always refer to something, when a reference is declared, it must be initialized with a
valid variable, as above. Using the declarations above, someInteger may be substituted
for anInteger everywhere. Literally, if you do something to someInteger, you're really
doing it to anInteger. You won't be using references in MacApp programming, except
for…
PARAMETER PASSING
The rules for Pascal parameter passing are something C++ programmers recite in their
sleep. What the Pascal compiler does for you, the C++ interface must be designed to
emulate. The rules are very simple:
- All VAR parameters are passed by pushing a pointer to the variable on the stack.
- Non-VAR parameters that are 4 bytes or smaller are passed by pushing a copy of
the variable directly on the stack.
- Non-VAR parameters that are larger than 4 bytes are passed by pushing a
pointer to the variable on the stack.
So, if you have a routine like FrameRect, "PROCEDURE FrameRect(aRect: Rect)", you
could declare it in C++ as "pascal void FrameRect(Rect* aRect)". Further, since aRect
is a value parameter, you can denote that it won't be altered by changing the declaration
to "pascal void FrameRect(const Rect* aRect)". This is perfectly legal, but has a minor
pitfall. Since all C++ knows that FrameRect wants a pointer passed, it is syntactically
legitimate to call "FrameRect(nil)". Guess what happens when you do that? What you
really want the compiler to do is pass FrameRect a pointer to a Rect and ensure that the
pointer in not nil-you want a reference to a Rect. To do this, the declaration then
becomes "pascal void FrameRect(const Rect& aRect)", which is just the way it's defined
in the C++ toolbox interfaces distributed with MacApp 3.0.
A BIT OF HISTORICAL IRONY
If you've been reading C++ interface files provided with MacApp 2.0.x and MPW, you
may be more than a bit confused. These products don't work with reference variables as
I've described above. Instead, they use the intermediate "pascal void FrameRect(const
Rect* aRect)" form. However, MacApp 3.0 ships with C++ interface files that use
reference variables. The hope is that the toolbox interfaces will be merged with the
MPW product at some time in the near future. In any case, MacApp 3.0 C++ coders will
use them.
By the way, this change absolutely guarantees that any existing C++ MacApp 2.0 code
will fail to compile under MacApp 3.0. Because C++ programmers have been passing
pointers to routines that now expect the real McCoy, all that code will need to be
revamped.
A side effect of using this convention of passing parameters is that the MacApp C++
sources have a distinctly Pascal-like flavor to them, as does MacApp 3.0 C++ code in
general. That is, I don't need to know whether a method can change a variable or not (i.e.
is it VAR?). I write my code the same way in either case; just write the name of the
variable and let the compiler worry about whether to push a pointer or a copy of the
variable. Pascal programmers should feel very comfortable with this situation since
this is exactly the convention used by the Pascal language.
NEXT TIME…
…I'll be looking at some real magic of Pascal and C++, and some of MacApp 3.0's new
features. Each language provides interesting and useful constructs, especially if you
happen to be programming in that language. I'm looking for those wonderful "How do you
do <feature of one language> in <the other language>?" and "Isn't there a better way?"
questions. As always, questions, comments, and other feedback are encouraged at
AppleLink: BERDAHL.