Important: The information in this document is obsolete and should not be used for new development.
Recipes--Objects
The recipes and sample code in this section describe how to define a class and how to create, initialize, and delete objects of that class type. They also show how to access inherited class types and how to determine whether an object descends from a specified class.Recipe--Defining a Class
This recipe demonstrates how to define a file-based document class, using theTIconDocument
class as an example.To define a subclass of
TFileBasedDocument
, you perform these steps:
The sample code shown in this recipe is from the IconEdit application.
- Provide a class definition.
- Provide runtime type information in the class implementation.
- Provide constructor and destructor methods.
- Provide an initialization method.
- Override additional methods.
Provide a Class Definition
If your document class uses PowerTalk to turn documents into electronic mail, it should descend from MacApp'sTMailingDocument
class. If it uses MacApp's Edition Manager support for publish and subscribe, it should descend fromTEditionDocument
. The IconEdit application does not support mailers or publish and subscribe, so its document class descends fromTFileBasedDocument
, which supports using streams to write data to disk and read it back.The definition of your document class should look something like the definition of the
TIconEditApplication
class:
class TIconDocument : public TFileBasedDocument { MA_DECLARE_CLASS; // RTTI: Place after class // declaration! private: TIconBitMap*fIconBitMap; // The icon's bitmap. TIconEditView*fIconView; // A reference to the single view // in the document window. CRGBColor fColor; // Color property of document. public: TIconDocument(); // Constructor. virtual ~TIconDocument(); // Destructor. void IIconDocument(TFile* itsDocument);// Initialization. virtual void DoMakeViews(Boolean forPrinting); . . // Not all methods shown. . }; // End TIconDocument.MA_DECLARE_CLASS is a macro that provides runtime type information for the class. If your class requires RTTI, this macro must be the first item in the class definition after the class declaration. Note that the destructor method,~TIconDocument
, is virtual. For more information on constructors and destructors, see Chapter 2, "Basic Operations."Provide Runtime Type Information in the Implementation
If your class requires RTTI, you add code like the following to the class implementation:
// Start of TIconDocument implementation. #undef Inherited #define Inherited TFileBasedDocument // Use pragma statement to specify code segment. Code segments are // necessary only for code built to run on 68K Macintosh computers. #pragma segment AOpen MA_DEFINE_CLASS_M1(TIconDocument, Inherited);The code shown here should appear at the beginning of your class implementation. You use the MA_DEFINE_CLASS_M1 macro, or one of its variations, to register the class so that the class can callInherited
to access methods of its parent classes, and so your application can create objects of the class type by name, ID, or signature. The MA_DEFINE_CLASS_M1 macro is described in "Registering Class Information," beginning on page 30.Provide Constructor and Destructor Methods
A constructor method sets the fields of its class to default or safe values. It has the same name as the class and is called automatically whenever an instance of the class is created with thenew
routine. The constructor for the icon document class is shown below. The#pragma segment
statement specifies a segment for the constructor, for 68K builds only. Code samples in this book generally omit the segment statement, but if your application will ever run on 68K-based machines, it is good practice to include a segment statement for each method in your application.
#pragma segment AOpen TIconDocument::TIconDocument() { // Set bitmap to NULL so that if IIconDocument intialization // fails, the TIconDocument::Free method will work correctly. fIconBitMap = NULL; // Set other fields to safe or initial values. fIconView = NULL; fColor = gRGBBlack; }An alternative approach that provides the same result is to initialize the document's fields with an initialization list:
TIconDocument::TIconDocument(): fIconBitMap(NULL), fIconView(NULL), fColor(gRGBBlack) { }A destructor method frees any memory allocated by the class and performs any other necessary cleanup. It has the same name as the class, preceded by a tilde symbol (~), and is called automatically whenever an instance of the class is freed with thedelete
routine. This is the destructor for TIconDocument:
TIconDocument::~TIconDocument() { // Dispose of the bitmap if it isn't NULL. fIconBitMap = (TIconBitMap*)FreeIfObject(fIconBitMap); }It isn't necessary to callInherited
in a constructor or destructor method. However, you should declare a destructor method with the key wordvirtual
if its base class has a virtual destructor. This ensures that all destructors in the class hierarchy are called automatically when an object of that type is deleted. For more information, see "Virtual Destructors," beginning on page 34.By convention, every MacApp class has at least a constructor method and a virtual destructor method, even if the methods are empty. Having them is not strictly necessary, but it prevents some C++ compilers from emitting pages of warning messages.
Provide an Initialization Method
The initialization method for your document class should perform the following tasks:
The following is the initialization method for the TIconDocument class:
- Call the initialization method of its parent class.
- Perform any initialization specific to your class that can't be handled safely in a constructor method.
For example, a constructor should not perform operations that may fail, such as allocating memory. If your document needs to allocate memory, it should do so in its initialization method.
void TIconDocument::IIconDocument(TFile* itsFile) { TIconBitMap* anIconBitMap; // Call parent's initialization. Pass file and scrap type values. this->IFileBasedDocument(itsFile, kSignature); // Install failure handler in case of problem creating bitmap. FailInfo fi; Try(fi) { // Code that might fail. // Allocate a new icon bitmap, initialize it, and store a // reference to it. Release failure handler if successful. anIconBitMap = new TIconBitMap; anIconBitMap->IIconBitMap(); fIconBitMap = anIconBitMap; fi.Success(); } else { // Code to recover from a failure. // Call Free to free the object. Destructor method will // free the bitmap, if one was created. this->Free(); // Propagate the failure to the next handler in the stack. fi.ReSignal(); } } // TIconDocument::IIconDocumentThe IIconDocument method calls IFileBasedDocument, to initialize all parent document classes. The constants passed to IFileBasedDocument specify a file type and a scrap type for the document.After calling IFileBasedDocument, the IIconDocument method performs any initialization specific to the icon document, which consists entirely of creating a new icon bitmap. A more complicated document class might require substantial additional code.
The failure handling in IIconDocument calls
this->Free()
to free the document if a failure occurs, then callsReSignal
to propagate the failure. Freeing the document causes its destructor method to be called, and the destructor forTIconDocument
frees the bitmap, if the document has one.
- Note
- This approach to failure handling is simple but not robust, so it may not be appropriate for all applications. A safer implementation would be for IIconDocument to free the bitmap directly, if necessary, then call
ReSignal
, allowing the calling routine (typically the application'sDoMakeDocument
method) to free the document object with its own failure handling.Override Additional Methods
You define subclasses to modify and extend the behavior of their parent classes. To do so, you add fields and methods and override virtual methods of the parent class. For example, a document class normally overrides theDoMakeViews
method, since MacApp's document classes can't know what kind of views your documents use.When you override a method, you add a declaration to the class definition in the header file and you add code for the method to the implementation file. The TIconDocument
::DoMakeViews
method is declared in the fileUIconEdit.h
:
virtual void DoMakeViews(Boolean forPrinting); // Override.The code for the TIconDocument::
DoMakeViews method, shown on the following page, is located in the implementation fileUIconEdit.cp
.
// TIconDocument::DoMakeViews: void TIconDocument::DoMakeViews(Boolean forPrinting) // Override. { TWindow* aWindow; TIconEditView* iconView; TStdPrintHandler*aPrintHandler; // If for printing, need only the view. if (forPrinting) { // Call global view server object to create the view. It returns a generic // view (TView), so we cast it to an icon-editing view. iconView = (TIconEditView *)gViewServer->DoCreateViews(this, NULL, kIconEditViewId, gZeroVPt); FailNIL(iconView); } // Otherwise, need view and window. else { FailNIL(aWindow = gViewServer->NewTemplateWindow(kIconWindowId, this)); // Get a reference to the view from the window. iconView = (TIconEditView*)(aWindow->FindSubView('ICON')); FailNIL(iconView); } // Save reference to view in document field. fIconView = iconView; // Create a print handler. aPrintHandler = new TStdPrintHandler; // Initialize the print handler. Pass "this" for a document reference. // Pass icon view for the view reference. Specify fixed horizontal and // vertical page size. aPrintHandler->IStdPrintHandler(this, iconView, !kSquareDots, kFixedSize, kFixedSize); } // TIconDocument::DoMakeViewsWhen the document is opened for printing, DoMakeViews creates an icon view only; otherwise, it creates a full window hierarchy.Recipe--Creating, Initializing, and Deleting an Object
This recipe shows how to
The sample code shown in this recipe uses the
- create and initialize an instance of a class
- delete the object when it is no longer needed
TIconDocument
class from the IconEdit application.Create and Initialize an Instance of a Class
You can create an instance of a class using thenew
operator:
TIconDocument* anIconDocument; anIconDocument = new TIconDocument;// Create a TIconDocument object. anIconDocument->IIconDocument(itsFile);// Initialize it.This code is from theTIconApplication::DoMakeDocument
method. The call tonew
results in a call to MacApp'sMAOperatorNew
routine to allocate the document object. The code shown here does not need to do any additional error checking becauseMAOperatorNew
callsFailNIL
on the allocated block of memory.Delete the Object When It Is No Longer Needed
You can delete an object when it is no longer needed by calling the object'sFree
method or by using MacApp'sFreeIfObject
routine. For example, theDoIt
method of aTCloseFileDocCommand
object makes the following call, after first saving the document (if necessary):
fDocument->CloseAndFree();TheTDocument::
CloseAndFree method in turn makes these two calls:
this->Close(); this->Free();TheFree
method ofTObject
calls theShallowFree
method, which callsdelete
. Thedelete
operator calls the object's destructor method, then callsMAOperatorDelete
, which attempts to free the object from the object heap.To delete an object with the convenience routine
FreeIfObject
, the destructor method ofTDocument
uses the following line:
fPrintInfo = (TPrintInfo*)FreeIfObject(fPrintInfo);FreeIfObject saves you a step by checking whether the passed object is equal toNULL
before callingFree
on the object. Since it returnsNULL
, it also allows you to assign the return value to the object that is freed, as in the line above. Note, however, that you must cast the return value to the type of the freed object.Don't use delete directly on an object that descends from
TObject
. CallingFree
or FreeIfObject instead gives theFree
method a chance to check whether the object is in use, and refrain from deleting it until it is safe to do so.Recipe--Dynamic Casting Between Class Types
MacApp provides a macro,MA_DYNAMIC_CAST
, that works together with the RTTI mechanism to provide safe dynamic casting--casting that is based on current, runtime class information. In fact, you can use theMA_DYNAMIC_CAST
macro to safely access any class type in an object's class hierarchy, even a type that is included through multiple inheritance. This is referred to as side casting. You can read about dynamic casting and side casting in Chapter 2, "Basic Operations."The
TMailingApplication::OpenOld
method uses the following dynamic cast:
MMailable* mailDoc = MA_DYNAMIC_CAST(MMailable, aDocument); if (mailDoc) mailDoc->ReadLetter(kForDisplay);The MA_DYNAMIC_CAST macro expands into code that determines whether the mixin classMMailable
is part of the class definition for aDocument. If so, the macro returns a reference to anMMailable
object; if not, it returnsNULL
. The code checks the returned value to see if the cast was successful.
- Note
- Since the MMailable class is a mixin class, this example demonstrates both side casting and dynamic casting.
Recipe--Determining Whether an Object Descends From a Specified Class
MacApp's RTTI mechanism, together with methods defined in theTObject
class, allows you to determine whether an object descends from a given class.To determine whether an object descends from a class, you use code like the following, from the
TDragDropBehavior::SetOwner
method:
#if qDebug // Verify that the owner is a TView. if (!fOwner->DescendsFrom(TView::GetClassDescStatic())) { ProgramBreak("###A TDragDropBehavior attached to a non-view object"); return; } #endif // qDebugMacApp usesDescendsFrom
to check for error conditions in debug versions of an application (hence theqDebug
compiler flag in the code above), but you can use it in nodebug versions as well. TheGetClassDescStatic
method returns aClassDesc
object containing RTTI for the class. You can call it for any class that includes RTTI.