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
MScriptableObject
andMDefaultScriptableObject
to help make other classes scriptable, and it uses theMMailable
andMMailing
classes to provide PowerTalk mailer support.
- Note
- Multiple inheritance should be used carefully. Suppose you define a class,
TYourShape
, that does not have its ownDraw
method, but that mixes in two classes,TPrimitiveShape
andTSimpleShape
, both of which do have aDraw
method. If your class calls theDraw
method, the compiler will complain about the ambiguous use of theDraw
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.
- test whether an object is a member of a class for safe casting
- use
Inherited
to 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
TheClassDesc
class works together with MacApp's RTTI macros to provide RTTI support. TheClassDesc
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'sTObject
class, theClassDesc
methodInitUClassDesc
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 macrosMA_DECLARE_CLASS
andMA_DEFINE_CLASS_M0
(or one of its variations). MacApp then makes that information available while the application is running. You use theMA_DECLARE_CLASS
macro in your class definition, with code similar to the following:
class TYourSubClass : public TParentClass { MA_DECLARE_CLASS;TheMA_DECLARE_CLASS
macro should be the first line within the declaration of the class. This macro adds fields and methods for dealing withClassDesc
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
, andMA_DEFINE_CLASS_M3
. TheM0
,M1
,M2
, orM3
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 ownMA_DEFINE_CLASS_M6
macro.Base classes such as
TObject
useMA_DEFINE_CLASS_M0
because they inherit from no other classes. TheTDocument
class, which inherits from its both parent classTCommandHandler
and 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_M1
macro (or any of its variants) stores class hierarchy information in the fields that were inserted by the declare class macro. The definition ofInherited
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();TheInitUClassDesc
method builds sorted dynamic arrays of RTTI data. (For more information onInitUObject
, see "Performing Additional Required Initialization," beginning on page 91.)The structures built by
InitUClassDesc
include 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
ClassDesc
objects for all classes used in the applicationfgClassDescListByName
- Points to a sorted dynamic array of
ClassDesc
objects sorted by class namefgClassDescListByID
- Points to a sorted dynamic array of
ClassDesc
objects sorted by class IDfgSignatures
- Points to a sorted dynamic array of
ClassDesc
objects containing object signatures and class IDsTObject
that use theMA_DECLARE_CLASS
andMA_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 theClassDesc
information 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_CLASS
andMA_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'sInitUAddorners
routine makes the following call:
MA_REGISTER_CLASS(TAdornerList);TheMA_REGISTER_CLASS
macro expands to a call to theCallRegisterClass
method of theClassDesc
field 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
NewByClassName
method without first calling theMA_REGISTER_CLASS
macro for that class, MacApp will display an alert box indicating that the class is not registered.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, theInitUAddorners
routine makes the following call:
MA_REGISTER_SIGNATURE(TDimAdorner, kDimAdorner);A signature is a 4-byte variable of typeIDType
, which is based onResType
. TheMA_REGISTER_SIGNATURE
macro passes the class name on to theMA_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 thenew
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 theMA_REGISTER_CLASS
orMA_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 theDoInitUMacApp
routine shows how theMA_REGISTER_CLASS
andMA_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
TheClassDesc
class 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 fromTObject
has anfClassID
field of typeClassID
. MacApp defines theClassID
type as ashort
, and sets thefClassID
field to a unique value in theIObject
method.MacApp defines the following type for storing classnames:
typedef CStr255 ClassName;MacApp provides theNewObjectBySignature
global routine to create objects by signature or by class name. If you pass a class name,NewObjectBySignature
callsClassDesc::NewByClassName
; if you pass only a class signature, it callsClassDesc::NewBySignature
.The
ClassDesc
class also provides methods for accessing signature, class ID, and class name information:GetClassName
,GetClassID
, andGetSignature
.And finally, the
ClassDesc
class 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. TheTMailableDocument
class descends directly fromTFileBasedDocument
,TDocument
, and other classes. It also inherits from two mixin classes,MMailable
(mixed in byTMailableDocument
itself) andMScriptableObject
(mixed in by theTDocument
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.
- 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
TMailableDocument
to typeTFileBasedDocument
is 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
TFileBasedDocument
to typeTMailableDocument
is a down cast.
theMailableDocument = (TMailableDocument*
) aFileBasedDocument;This type of cast is not necessarily safe--not every instance of
TFileBasedDocument
is 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
MMailable
to typeTFileBasedDocument
is a side cast.
theFileBasedDocument = (TFileBasedDocument*
) aMailable;As with a down cast, a side cast is not necessarily safe--not every instance of
MMailable
is 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_CAST
macro works together with MacApp's RTTI mechanism to provide safe dynamic casting based on current runtime class information. You passMA_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 returnsNULL
.The
TFileBasedLetter::IFileBasedLetter
method provides an example of how to the useMA_DYNAMIC_CAST
macro:
void TFileBasedLetter::IFileBasedLetter(MMailable* mailDoc) { ILetter(mailDoc); fFileBasedDocument = MA_DYNAMIC_CAST(TFileBasedDocument, mailDoc); FailNonObject(fFileBasedDocument); }TheMA_DYNAMIC_CAST
macro expands into code that gets theClassDesc
record of the mailDoc object, and calls itsDynamicCast
method. If the class descends fromTFileBasedDocument
, MA_DYNAMIC_CAST casts theMMailable
pointer into a pointer to the actual base class (TMailableDocument
), and casts that into a pointer to theTFileBasedDocument
portion in theTMailableDocument
object; 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 thedelete
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 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
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.