MicroApp 2
Volume Number: 6
Issue Number: 1
Column Tag: Jörg's Folder
C++ Micro- application 
By Jörg Langowski, MacTutor Editorial Board
Note: Source code files accompanying article are located on MacTech CD-ROM orsource code disks.
“C++ micro- application, part 2”
We’ll expand the micro-application that I presented last month by adding more
functionality to the windows this time. But first, let me review some discussions that
started on our Calvacom bulletin board after my first introduction to C++. The way I
defined my matrix operations, it seems, wasn’t totally in the C++ spirit; there was a
reason for this, which I forgot to explain.
The problem is that our matrix operators must return a result somehow. If you
define the operator the intuitive way:
// 1
matrix matrix::operator* (matrix& a)
{
if (cols != a.rows)
error(“class matrix: size mismatch in *”);
matrix c(rows,a.cols);
for (register int i=0 ; i
for (register int j=0 ; j {
register float sum = 0;
for (register int k=0 ; k
sum = sum + elem(i,k)*a.elem(k,j);
c(i,j) = sum;
}
return c;
}
the result matrix is allocated locally first, the product computed and stored into the
local object, and the whole matrix is copied as soon as it is assigned to another variable
of type matrix- like in matrix x = a*b, where x is newly created in the assignment, or
in x = a*b, where x has been defined previously. That the local object is destroyed as
soon as one leaves the operator’s scope and a copy be made for use in the scope that
called the operator is required by C++. But the copy operation may take a long time
with big arrays, and in most cases it won’t be necessary because the result has already
been computed and the memory allocated.
One solution - which I chose - was not to return an object as a result, but a
reference to it:
//2
matrix& matrix::operator* (matrix& a)
{
if (cols != a.rows)
error(“class matrix: size mismatch in *”);
matrix& c = *new matrix(rows,a.cols);
for (register int i=0 ; i
for (register int j=0 ; j {
register float sum = 0;
for (register int k=0 ; k
sum = sum + elem(i,k)*a.elem(k,j);
c(i,j) = sum;
}
return c;
}
Here, the space for the result is allocated through the new() operation, which
creates memory space that is not automatically destroyed as one leaves the operator’s
context. Now, however, the system will never know when to free the space associated
with the matrix, unless you tell it to do so by calling delete(). This precludes usage of
those operators in any complex formulas, because intermediate results will be stored
in matrix-type objects that are never accessible and can therefore never be deleted.
Alain Richard (on Calvacom) has raised these points, and others:
“Constructors/destructors allow the use of data structures without having to
care about initialization/ termination code. For instance, a first version of a class X
might not need such code while the second version might need it; a program using class
X can be recompiled without modification in both cases.
When a result of type matrix is produced, a 500*500 matrix is not passed
back through the stack; only the data structure which defines it and contains a pointer
to the data. The only problem is that the constructor will often copy the data
unnecessarily; but the current definition of C++ doesn’t allow a better solution.”
Alain proposes to use procedures instead of operators for functions in which the
programmer has to clean up intermediate results explicitly. One possibility, but this
gives up the last advantage of operators, shortness of notation.
He has some more comments on bugs and deficiencies:
“In C++, the only way to pass a result is through the return statement, even
though the result is a function parameter. However, since one doesn’t know its name,
one has to go through an intermediate variable R and then copy R into the actual result.
One should also note that the code that CFront generates isn’t very well
optimized (a euphemism), or that the preprocessor relies too much on the efficiency
of the C compiler, in our case MPW C 3.1b1. Some observations I made in the code
generated by CFront:
CFront doesn’t optimize the use of intermediate variables and creates them for
each new expression. Then, these variables are freed only at the end of the block
containing the expression, which is stupid because it blocks memory.
A lot of dead code is generated by CFront. Usually, such code should be removed by
the C compiler, but that is unfortunately not the case. For instance:
//3
struct A { A() {} };
struct B:A { B() {} };
main()
{
B b;
}
The declaration of b causes 30 lines of assembler code to be generated which don’t
do anything [in fact, when I checked it there seemed to be even more dead code - JL].
For the maintenance of virtual methods of a class A, CFront creates a method
dispatch table. The table, named __A_vtbl, is stored together with the application
code. In addition, CFront creates a global variable __A_ptbl, a pointer to __A_vtbl.
This global is later used to initialize a hidden instance of each variable of type A.
__A_vtbl and __A_ptbl are stored as globals, which prohibits the use of classes for
DAs, CDEFs, LDEFs, etc.
I have found one or two bugs: it is not always possible to declare a variable inside
an expression (produces a syntax error). There is also a problem with pointers to
base class member functions in the case of multiple inheritance. That error is easy to
circumvent but causes a run time, not a compilation error.
This is not an exhaustive list, but unfortunately shows that the MPW compilers
are not quite mature yet. But even with the bad code quality, CFront does at least exist.
At any rate, C++ is going to be the most important development language for the next
years.”
I almost agree with the last statement - however, I am also getting very curious
about Eiffel, a new OOP-language which is rumored to be available for the Mac next
year.
Zoom, grow and scroll for MacTutorApp
The skeleton application that I presented last month did not do much; the window
could not be resized or zoomed, and there were no controls such as scroll bars present.
This month we’ll expand the application by adding these functions. We’ll use the
definitions that we made last time, and create a new subclass of MacTutorDocument,
MacTutorGrow.
The interest of the object-oriented approach is, of course, that we can reuse most
of the code that we have already defined in the previous example. Only those functions
which are specific to our new document will have to be redefined.
Placeholders for these functions exist already in the TDocument class definition,
which is part of the C++ examples that come with Apple’s C++ system. That definition
is quite long, an we don’t need to reprint it here; all I show (listing 1) is the header
file that constitutes the interface. You see that entries exist for methods such as
DoContent, DoGrow and DoZoom. These methods will be called from the main event loop,
so if you define your own document class with a non-empty DoContent method and a
mouse click occurs inside the content region of the document window, that method will
be called automatically.
Therefore, the changes we have to do to previous month’s example are quite
simple: the main program will still be an object of type TMacTutorApp, but the
document opened will be of a new class, which we call TMacTutorGrow. Then we only
have to change a few lines in the main program definition of TMacTutorApp, as you see
in listing 3. If someone had already defined a document class for us that handles
scrolling and resizing, that is all we would have to do to make the application work
with that new type of document window.
Unfortunately, we still have to do that work, and I won’t claim I did a perfect job
of scrolling the display message around in the example window; window resizing and
drawing of the controls works OK, but the scrolling still leaves something to be
desired. Only when the window actually gets redrawn on an update event is the contents
displayed correctly. You’re, of course, invited to improve on my example.
Listing 2 shows the definition of the TMacTutorGrow class - the header file -
followed by the actual code. We derive TMacTutorGrow from TMacTutorDocument (see
last column), adding some private variables and methods, and overriding some of the
public methods in TDocument.
The first thing to note is that we define some functions that are not methods of any
particular class for scroll bar handling (this is the approach Apple took for its
TEDocument example). To indicate to the compiler that these functions are defined in C,
their prototype definitions are enclosed in curly brackets and prefixed by extern “C”.
This is a feature of the 2.0 implementation of C++ and not mentioned in Stroustrup’s
book.
The class implementation follows, first we need to define the constructor and
destructor. The constructor of a subclass is called after the constructor of its ancestor;