Previous Book Contents Book Index Next

Inside Macintosh: Programmer's Guide to MacApp / Part 1 - MacApp Theory and Architecture
Chapter 2 - Basic Operations


Multiple Inheritance

Multiple inheritance refers to the ability of a class to inherit behavior (fields and methods) from more than one parent class. A class that is added to the class hierarchy of another class through multiple inheritance is called a mixin class. Because MacApp is written in C++, it allows multiple inheritance. Several of MacApp's key classes use mixin classes in their definitions.

To help support multiple inheritance, MacApp

To take full advantage of MacApp's support for multiple inheritance, your classes must use MacApp's RTTI macros and define virtual destructor methods. MacApp itself uses the mixin classes MScriptableObject and MDefaultScriptableObject to help make other classes scriptable, and it uses the MMailable and MMailing classes to provide PowerTalk mailer support.

Note
Multiple inheritance should be used carefully. Suppose you define a class, TYourShape, that does not have its own Draw method, but that mixes in two classes, TPrimitiveShape and TSimpleShape, both of which do have a Draw method. If your class calls the Draw method, the compiler will complain about the ambiguous use of the Draw method.
Problems like this tend to occur in large projects, where many programmers contribute to the code base. Design reviews can be an effective countermeasure.

Runtime Type Information

Runtime type information (RTTI) refers to runtime information, or metadata, about the class types and inheritance relationships of application objects. In MacApp, RTTI is based on macros--MacApp uses the macros in its class definitions, and you use them in any classes you define that need RTTI.

The main reasons you might use RTTI information are to

MacApp's RTTI macros add overhead to an application. If you don't need to do any of these things with a class, you don't need to set up RTTI for the class.

The following sections describe how MacApp implements RTTI support and how you add RTTI information to your classes.

The ClassDesc Class

The ClassDesc class works together with MacApp's RTTI macros to provide RTTI support. The ClassDesc class contains static global fields that point to sorted dynamic arrays of class name and inheritance data. It also contains methods for accessing the stored data. During static initialization, which occurs before any other application code is executed, MacApp's RTTI macros store class hierarchy data in each class that uses the macros. Later, during initialization of MacApp's TObject class, the ClassDesc method InitUClassDesc uses the stored data to construct its arrays. To include RTTI information in your classes, you use the macros described in the next section.

The ClassDesc class also provides methods for creating new objects based on its stored class information. These methods are described in "Creating New Objects by Signature, Class ID, or Class Name," beginning on page 31.

Inserting RTTI Information Into Your Classes

To insert RTTI information into your classes, you use the macros MA_DECLARE_CLASS and MA_DEFINE_CLASS_M0 (or one of its variations). MacApp then makes that information available while the application is running. You use the MA_DECLARE_CLASS macro in your class definition, with code similar to the following:

class TYourSubClass : public TParentClass
{
   MA_DECLARE_CLASS;
The MA_DECLARE_CLASS macro should be the first line within the declaration of the class. This macro adds fields and methods for dealing with ClassDesc information. MacApp uses these fields and methods during program initialization to build sorted arrays of RTTI data and, at runtime, to supply information about your classes.

You use the MA_DEFINE_CLASS_M1 macro (or one of its variants) in your class implementation, with code similar to the following:

#undef Inherited
#define Inherited TParentClass
#pragma segment YourSegmentName
MA_DEFINE_CLASS_M1(TYourSubClass, Inherited);
This code should be the first thing in the class implementation, appearing before the implementation of any methods.

MacApp provides four variations of the MA_DEFINE_CLASS_M1 macro: MA_DEFINE_CLASS_M0, MA_DEFINE_CLASS_M1, MA_DEFINE_CLASS_M2, and MA_DEFINE_CLASS_M3. The M0, M1, M2, or M3 variations indicate the number of classes from which the current class inherits. Since the definition of these macros is included in MacApp, you can extend these macros by creating a new macro based on those provided by MacApp. For example, if you have a class that inherits from six other classes, you can define your own MA_DEFINE_CLASS_M6 macro.

Base classes such as TObject use MA_DEFINE_CLASS_M0 because they inherit from no other classes. The TDocument class, which inherits from its both parent class TCommandHandler and from the multiple inheritance class MScriptableObject, uses MA_DEFINE_CLASS_M2:

#undef Inherited
#define Inherited TCommandHandler
#pragma segment MAOpen
MA_DEFINE_CLASS_M2(TDocument, Inherited, MScriptableObject);
When expanded, the MA_DEFINE_CLASS_M1 macro (or any of its variants) stores class hierarchy information in the fields that were inserted by the declare class macro. The definition of Inherited allows you to use code like the following:

void TYourSubClass::SomeMethod()
{
   // Do something.
   // Then let parent class do something.
   Inherited::SomeMethod(); // Calls TParentClass method.
}

Initializing RTTI Information

MacApp's RTTI fields are set to default or initial values during static initialization. Later, during initialization of MacApp, InitUObject calls

ClassDesc::InitUClassDesc();
The InitUClassDesc method builds sorted dynamic arrays of RTTI data. (For more information on InitUObject, see "Performing Additional Required Initialization," beginning on page 91.)

The structures built by InitUClassDesc include the following fields:

fgClassDescList
Points to the head of a linked list of ClassDesc objects for all classes used in the application
fgClassDescListByName
Points to a sorted dynamic array of ClassDesc objects sorted by class name
fgClassDescListByID
Points to a sorted dynamic array of ClassDesc objects sorted by class ID
fgSignatures
Points to a sorted dynamic array of ClassDesc objects containing object signatures and class IDs
These dynamic arrays can be searched to provide class metadata for all descendants of TObject that use the MA_DECLARE_CLASS and MA_DEFINE_CLASS_M0 (or one of its variations) macros. The fields aren't intended for use by your application--in fact, most of them are private. Your application can generally access the ClassDesc information it needs through methods of TObject (see "RTTI Fields and Methods," beginning on page 35) or ClassDesc ("Creating New Objects by Signature, Class ID, or Class Name," beginning on page 31), or by calling macros such as MA_DYNAMIC_CAST ("Dynamic Casting," beginning on page 32).

The next section describes how your classes can take advantage of MacApp's RTTI support.

Registering Class Information

You register class information for a class so that you can create a new instance of the class by name, by class ID, or by class signature. MacApp provides two macros for this purpose, MA_REGISTER_CLASS and MA_REGISTER_SIGNATURE.

You call the MA_REGISTER_CLASS macro, passing a class name, to register the class. Registering a class enables your application to create new objects of that class type by name or by class ID. For example, MacApp's InitUAddorners routine makes the following call:

MA_REGISTER_CLASS(TAdornerList);
The MA_REGISTER_CLASS macro expands to a call to the CallRegisterClass method of the ClassDesc field for the class:

ClassDesc::CallRegisterClass(&TAdornerList::fgClassDesc,
                     &TAdornerList::_DefaultConstructor);
The default constructor passed in this call creates an adorner by calling

new TAdornerList;
IMPORTANT
If you try to create an instance of a class using the NewByClassName method without first calling the MA_REGISTER_CLASS macro for that class, MacApp will display an alert box indicating that the class is not registered.
You call the MA_REGISTER_SIGNATURE macro, passing a class name and a class signature, to register the class so that your application can create new objects of that class type by signature, by name, or by class ID. For example, the InitUAddorners routine makes the following call:

MA_REGISTER_SIGNATURE(TDimAdorner, kDimAdorner);
A signature is a 4-byte variable of type IDType, which is based on ResType. The MA_REGISTER_SIGNATURE macro passes the class name on to the MA_REGISTER_CLASS macro, so you can also create objects of the registered type by name or class ID.

When you register a class, MacApp guarantees that the linker will include code for that class in your application, as described in the next section.

Including Code for a Class

A linker is said to dead strip code when it removes code that appears to be unused. When a class is instantiated directly with the new call, the linker knows the class is used in the application. But MacApp uses resource templates to create views and windows, and most developers use resource templates to define and instantiate their own views as well. However, if a class is registered with the MA_REGISTER_CLASS or MA_REGISTER_SIGNATURE macro, the linker will have a reference to a function for creating an instance of the class and will always include the code for that class.

Your application must register all classes it uses that are not created by a specific new call. The following code fragment from the DoInitUMacApp routine shows how the MA_REGISTER_CLASS and MA_REGISTER_SIGNATURE macros are used to register MacApp classes:

MA_REGISTER_CLASS(TDragDropBehavior);
MA_REGISTER_SIGNATURE(TView, kStdView);

Creating New Objects by Signature, Class ID, or Class Name

The ClassDesc class provides methods for creating new objects by signature, by class ID, or by class name: NewBySignature, NewByClassID, and NewByClassName. MacApp defines the 4-byte IDType for signatures:

typedef ResType IDType;
Specific signatures are defined by your application or by MacApp. For example, MacApp defines the following signature for a default view type:

const IDType kIDDefaultView = 'DFLT';// View ID of default view.
Each class that descends from TObject has an fClassID field of type ClassID. MacApp defines the ClassID type as a short, and sets the fClassID field to a unique value in the IObject method.

MacApp defines the following type for storing classnames:

typedef CStr255 ClassName;
MacApp provides the NewObjectBySignature global routine to create objects by signature or by class name. If you pass a class name, NewObjectBySignature calls ClassDesc::NewByClassName; if you pass only a class signature, it calls ClassDesc::NewBySignature.

The ClassDesc class also provides methods for accessing signature, class ID, and class name information: GetClassName, GetClassID, and GetSignature.

And finally, the ClassDesc class provides methods for converting between one kind of class information and another: GetClassDescFromClassID, GetClassDescFromClassName, and GetClassDescFromSignature.

MacApp itself creates objects using each of the new methods listed above (NewBySignature, NewByClassID, and NewByClassName). For example, when you define a view hierarchy with a resource editor and create the hierarchy in your application by calling NewTemplateWindow (see "Create a Window From the 'View' Resource," beginning on page 452), MacApp creates the objects in the hierarchy either by signature or by class name.

Creating new objects by signature has distinct advantages over creating by class name or class ID. Four-byte signatures are smaller than most class names--they take up less space and contribute to faster lookup. And unlike class IDs, signatures don't change unless you change them. (Class IDs are generated in sequence each time the application is run, based on the order in which the application's objects are initialized. When you modify your application, the class ID numbers can change.)

Dynamic Casting

In an object-oriented environment with multiple inheritance, casting can take several forms, including the up cast, the down cast, and the side cast. Consider the document class hierarchy shown in Figure 7-1 on page 164. The TMailableDocument class descends directly from TFileBasedDocument, TDocument, and other classes. It also inherits from two mixin classes, MMailable (mixed in by TMailableDocument itself) and MScriptableObject (mixed in by the TDocument class).

Casting can lead to subtle or not-so-subtle errors. A cast may coerce an object pointer to be interpreted as a pointer to a type that is not part of the object's class hierarchy. As a result, the code that follows the cast may call a method or attempt to access data that doesn't exist for the pointed-to object. The results may vary but can be assumed to be bad.

Fortunately, MacApp provides a mechanism to perform safe object casting for all types of casts:

The MA_DYNAMIC_CAST macro works together with MacApp's RTTI mechanism to provide safe dynamic casting based on current runtime class information. You pass MA_DYNAMIC_CAST a desired result type and an object reference. If the desired type exists in the object's class hierarchy, through either direct or multiple inheritance, MA_DYNAMIC_CAST casts the object to the desired type; otherwise, it returns NULL.

The TFileBasedLetter::IFileBasedLetter method provides an example of how to the use MA_DYNAMIC_CAST macro:

void TFileBasedLetter::IFileBasedLetter(MMailable* mailDoc)
{
   ILetter(mailDoc);
   fFileBasedDocument = MA_DYNAMIC_CAST(TFileBasedDocument,
                                 mailDoc);
   FailNonObject(fFileBasedDocument);
}
The MA_DYNAMIC_CAST macro expands into code that gets the ClassDesc record of the mailDoc object, and calls its DynamicCast method. If the class descends from TFileBasedDocument, MA_DYNAMIC_CAST casts the MMailable pointer into a pointer to the actual base class (TMailableDocument), and casts that into a pointer to the TFileBasedDocument portion in the TMailableDocument object; otherwise, MA_DYNAMIC_CAST returns NULL.

For objects that contain runtime type information, you can use MacApp's dynamic casting mechanism to safely perform up casts, down casts, or side casts.

Virtual Destructors

When you use the delete operator to dispose of an object, the object's destructor method is called automatically. However, to ensure that all destructors in the object's class hierarchy are called, including those mixed in by multiple inheritance, the destructors should be declared using the keyword virtual.

Declaring a constructor as virtual creates a small amount of extra overhead, but it may help prevent obscure memory leaks due to objects not being freed completely. MacApp's TObject class and its descendants use virtual destructor methods to help guarantee proper object disposal.

For more information on freeing objects, see "Creating and Deleting Objects," beginning on page 38.


Previous Book Contents Book Index Next

© Apple Computer, Inc.
25 JUL 1996