Important: The information in this document is obsolete and should not be used for new development.
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
- defines macros and functions for creating objects dynamically by signature, class ID, or class name
- defines macros for efficient stripping of unused classes to create smaller applications
- defines a macro for dynamic casting that works correctly with multiple inheritance
- defines virtual destructor methods to support correct freeing when an object is deleted
MScriptableObjectandMDefaultScriptableObjectto help make other classes scriptable, and it uses theMMailableandMMailingclasses to provide PowerTalk mailer support.
- Note
- Multiple inheritance should be used carefully. Suppose you define a class,
TYourShape, that does not have its ownDrawmethod, but that mixes in two classes,TPrimitiveShapeandTSimpleShape, both of which do have aDrawmethod. If your class calls theDrawmethod, the compiler will complain about the ambiguous use of theDrawmethod.![]()
- 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.
- test whether an object is a member of a class for safe casting
- use
Inheritedto call a parent-class version of a method (illustrated in Figure 5-5 on page 111)- create new objects by class name, ID, or signature
The following sections describe how MacApp implements RTTI support and how you add RTTI information to your classes.
The ClassDesc Class
TheClassDescclass works together with MacApp's RTTI macros to provide RTTI support. TheClassDescclass 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'sTObjectclass, theClassDescmethodInitUClassDescuses the stored data to construct its arrays. To include RTTI information in your classes, you use the macros described in the next section.The
ClassDescclass 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 macrosMA_DECLARE_CLASSandMA_DEFINE_CLASS_M0(or one of its variations). MacApp then makes that information available while the application is running. You use theMA_DECLARE_CLASSmacro in your class definition, with code similar to the following:
class TYourSubClass : public TParentClass { MA_DECLARE_CLASS;TheMA_DECLARE_CLASSmacro should be the first line within the declaration of the class. This macro adds fields and methods for dealing withClassDescinformation. 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_M1macro (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_M1macro:MA_DEFINE_CLASS_M0,MA_DEFINE_CLASS_M1,MA_DEFINE_CLASS_M2, andMA_DEFINE_CLASS_M3. TheM0,M1,M2, orM3variations 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 ownMA_DEFINE_CLASS_M6macro.Base classes such as
TObjectuseMA_DEFINE_CLASS_M0because they inherit from no other classes. TheTDocumentclass, which inherits from its both parent classTCommandHandlerand from the multiple inheritance classMScriptableObject, usesMA_DEFINE_CLASS_M2:
#undef Inherited #define Inherited TCommandHandler #pragma segment MAOpen MA_DEFINE_CLASS_M2(TDocument, Inherited, MScriptableObject);When expanded, theMA_DEFINE_CLASS_M1macro (or any of its variants) stores class hierarchy information in the fields that were inserted by the declare class macro. The definition ofInheritedallows 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,InitUObjectcalls
ClassDesc::InitUClassDesc();TheInitUClassDescmethod builds sorted dynamic arrays of RTTI data. (For more information onInitUObject, see "Performing Additional Required Initialization," beginning on page 91.)The structures built by
InitUClassDescinclude the following fields:
These dynamic arrays can be searched to provide class metadata for all descendants of
fgClassDescList- Points to the head of a linked list of
ClassDescobjects for all classes used in the applicationfgClassDescListByName- Points to a sorted dynamic array of
ClassDescobjects sorted by class namefgClassDescListByID- Points to a sorted dynamic array of
ClassDescobjects sorted by class IDfgSignatures- Points to a sorted dynamic array of
ClassDescobjects containing object signatures and class IDsTObjectthat use theMA_DECLARE_CLASSandMA_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 theClassDescinformation it needs through methods ofTObject(see "RTTI Fields and Methods," beginning on page 35) orClassDesc("Creating New Objects by Signature, Class ID, or Class Name," beginning on page 31), or by calling macros such asMA_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_CLASSandMA_REGISTER_SIGNATURE.You call the
MA_REGISTER_CLASSmacro, 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'sInitUAddornersroutine makes the following call:
MA_REGISTER_CLASS(TAdornerList);TheMA_REGISTER_CLASSmacro expands to a call to theCallRegisterClassmethod of theClassDescfield for the class:
ClassDesc::CallRegisterClass(&TAdornerList::fgClassDesc, &TAdornerList::_DefaultConstructor);The default constructor passed in this call creates an adorner by calling
new TAdornerList;You call the
- IMPORTANT
- If you try to create an instance of a class using the
NewByClassNamemethod without first calling theMA_REGISTER_CLASSmacro for that class, MacApp will display an alert box indicating that the class is not registered.![]()
MA_REGISTER_SIGNATUREmacro, 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, theInitUAddornersroutine makes the following call:
MA_REGISTER_SIGNATURE(TDimAdorner, kDimAdorner);A signature is a 4-byte variable of typeIDType, which is based onResType. TheMA_REGISTER_SIGNATUREmacro passes the class name on to theMA_REGISTER_CLASSmacro, 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 thenewcall, 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 theMA_REGISTER_CLASSorMA_REGISTER_SIGNATUREmacro, 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
newcall. The following code fragment from theDoInitUMacApproutine shows how theMA_REGISTER_CLASSandMA_REGISTER_SIGNATUREmacros 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
TheClassDescclass provides methods for creating new objects by signature, by class ID, or by class name:NewBySignature,NewByClassID, andNewByClassName. 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 fromTObjecthas anfClassIDfield of typeClassID. MacApp defines theClassIDtype as ashort, and sets thefClassIDfield to a unique value in theIObjectmethod.MacApp defines the following type for storing classnames:
typedef CStr255 ClassName;MacApp provides theNewObjectBySignatureglobal routine to create objects by signature or by class name. If you pass a class name,NewObjectBySignaturecallsClassDesc::NewByClassName; if you pass only a class signature, it callsClassDesc::NewBySignature.The
ClassDescclass also provides methods for accessing signature, class ID, and class name information:GetClassName,GetClassID, andGetSignature.And finally, the
ClassDescclass provides methods for converting between one kind of class information and another:GetClassDescFromClassID,GetClassDescFromClassName, andGetClassDescFromSignature.MacApp itself creates objects using each of the new methods listed above (
NewBySignature,NewByClassID, andNewByClassName). For example, when you define a view hierarchy with a resource editor and create the hierarchy in your application by callingNewTemplateWindow(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. TheTMailableDocumentclass descends directly fromTFileBasedDocument,TDocument, and other classes. It also inherits from two mixin classes,MMailable(mixed in byTMailableDocumentitself) andMScriptableObject(mixed in by theTDocumentclass).
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.
- An up cast is a cast of an object to another class type higher in the object's class hierarchy. Casting an object of type
TMailableDocumentto typeTFileBasedDocumentis an up cast:
theFileBasedDocument = (TFileBasedDocument*) aMailableDocument;This is the most common and safest type of object cast. If the object reference aMailableDocument truly points to an instance of
TMailableDocument, it can always be safely cast to an instance ofTFileBasedDocument.- A down cast is a cast of an object to another class type lower in the object's class hierarchy. Casting an object of type
TFileBasedDocumentto typeTMailableDocumentis a down cast.
theMailableDocument = (TMailableDocument*) aFileBasedDocument;This type of cast is not necessarily safe--not every instance of
TFileBasedDocumentis also an instance ofTMailableDocument.- A side cast is a cast of an object to another class type that may be mixed into the object's class hierarchy. Casting an object of type
MMailableto typeTFileBasedDocumentis a side cast.
theFileBasedDocument = (TFileBasedDocument*) aMailable;As with a down cast, a side cast is not necessarily safe--not every instance of
MMailableis also an instance ofTMailableDocument.
Fortunately, MacApp provides a mechanism to perform safe object casting for all types of casts:
The
- A dynamic cast is performed at runtime and uses current information to determine whether an object can be safely cast to the desired class.
MA_DYNAMIC_CASTmacro works together with MacApp's RTTI mechanism to provide safe dynamic casting based on current runtime class information. You passMA_DYNAMIC_CASTa 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_CASTcasts the object to the desired type; otherwise, it returnsNULL.The
TFileBasedLetter::IFileBasedLettermethod provides an example of how to the useMA_DYNAMIC_CASTmacro:
void TFileBasedLetter::IFileBasedLetter(MMailable* mailDoc) { ILetter(mailDoc); fFileBasedDocument = MA_DYNAMIC_CAST(TFileBasedDocument, mailDoc); FailNonObject(fFileBasedDocument); }TheMA_DYNAMIC_CASTmacro expands into code that gets theClassDescrecord of the mailDoc object, and calls itsDynamicCastmethod. If the class descends fromTFileBasedDocument, MA_DYNAMIC_CAST casts theMMailablepointer into a pointer to the actual base class (TMailableDocument), and casts that into a pointer to theTFileBasedDocumentportion in theTMailableDocumentobject; otherwise, MA_DYNAMIC_CAST returnsNULL.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 thedeleteoperator 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 keywordvirtual.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
TObjectclass 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.