![]() |
|||||||||||||||||||||||||||||||||||||||||||||
3.4 Communicating Objects by Reference and By Pointer |
|||||||||||||||||||||||||||||||||||||||||||||
Three major structures are built using objects communicated by reference or by pointer::
All of these uses will be explored in this section. Additional features of associations are presented in the next two sections. The difference between communicating objects by reference or by pointer is largely syntactic, as they each accomplish the important goal of passing original, not copied, objects. In C++, the ampersand symbol (&) is used to denote communicating an object by reference and the asterisk symbol (*) is used to denote communicating an object by pointer. Remeber, though, that these two symbols have several meanings in C++ depending on the context in which they are used, so to indicate communicating objects by reference or by pointer, the symbols "&" and "*" must appear after a class name in a parameter list or in a return type. Result Parameters Passing an object as a parameter by reference or by pointer allows the sender to see changes made to the object by the receiver. In some cases the object is used purely in an output form, that is, the object's initial value is not used by the receiver. In other cases the receiver uses the object in an input-output form, that is, the receiver uses the initial value of the parameter object and the receiver possibly changes the object in some way. This latter use will be illustrated below. A simple information-retrieval example illustrates how objects are passed by reference and by pointer. In this example, a simple text file will be searched for the first line containing a given search string. Various applications can be built on this model, among them:
Many other examples follow the same basic organization. The simple information-retrieval example extends the File class by adding a method to search a file object for a given search string. An information retrieval query is represented by the following class:
The Query class contains two text strings, a search string, and a result string, each of which has an associated pair of accessor and mutator methods to set and get the string's value. The two "get" methods return a null pointer if their respective strings are undefined. The AskUser method initiates a dialogue that allows the user to provide a search string. This design anticipates that the sender defines the search string (via the constructor, the SetSearch method, or the AskUser method), that the receiver doing the search defines the result string (via the SetResult method), and that the sender retrieves the result string (via the GetResult method). The File class is extended by adding a method to search a File object for a given query, modifying the Query object if the search is successful. The additions to the File class are:
class File {
private:
public:
...
void SearchFor (Query& q); // by reference
void SearchFor (Query* q); // by pointer
...
};
The specification "Query&" denotes passing a Query object by reference. The specification "Query*" denotes passing a Query object by pointer. In neither case is the Query object copied. The Query class and the extended File class can be used as shown below. This code also illustrates the syntactic differences between by reference and by pointer objects.
Query query1("object"), query2("oriented");
Query *query3;
Query *query4;
query3 = new Query("programming");
query4 = new Query("C++");
File booklist("booklist");
booklist.SearchFor( query1); // by reference
booklist.SearchFor(&query2); // by pointer
booklist.SearchFor( query3); // by pointer
booklist.SearchFor(*query4); // by reference
char* result1 = query1.GetResult();
char* result2 = query2.GetResult();
char* result3 = query3->GetResult();
char* result4 = query4->GetResult();
In this example, query1 and query2 are names for objects, while query3 and query4 are pointers to objects. The first use of the SearchFor method, using query1, is by reference because query1 is an object. Passing the query1 object matches with the by reference overloading of the SearchFor method. The second use of the SearchFor method, using query2, is by pointer because the parameter, "&query2" is a pointer. The "&" operator in this context is the "address of" operator. Thus, what is passed is the "address of query2" (i.e., a pointer to the query2 object) which matches the by pointer overloading of the SearchFor method. The third use of the SearchFor method, using query3, is also by pointer because query3 is, by its declaration, a pointer to a Query object. The fourth use of the SearchFor method, using query4, is by reference. While query4 is a pointer to a Query object, the actual parameter is "*query4." The "*" operator is the dereference operator yielding the object that is being pointed to. Thus, the actual parameter is an object. When the actual parameter is an object, it will match with the by reference overloading of the SearchFor method. The following table summarizes the syntactic aspects of passing
arguments by reference and by pointer. The first line of the table
indicates that, moving left to right,
The first row corresponds to the first use of the SearchFor method using the query1 object in the example code above. The subsequent rows in the table correspond to query2, query3, and query4, respectively.
Managers Manager objects commonly return objects by reference or by pointer. In some cases the manager object is responsible for creating and distributing objects of another class. In this role, managers are also called "factory" objects. In other cases the manager object is responsible for maintaining a set of managed objects, organizing them according to some attribute (e.g, alphabetically by a string attribute), or finding a managed object with a certain attribute value (e.g., a given name). In this role, managers are also called "collections." Manager objects are able to simplify a system by concealing the design decisions surrounding the construction, duplication, indexing, storage, and distribution of the managed objects. Other parts of the system are able to use the managed objects with less concern for the low-level management details. To fulfill its role as a manager, pointers and references are essential because maintaining a collection of copies of the managed objects is usually pointless; it is the original objects that are to be managed. Two examples are given to illustrate the use of pointers and references in defining managers. The first example extends the File class so that a file object can return a file stream object for the underlying disk file or a text window in which the file can be viewed. In this example, the File object acts as a factory that produces a particular kind of file stream or text window object. The second example, illustrating the concept of a collection, defines a Frame manager that maintains a set of Frame objects. The Frame manager is similar in concept to the "window manager" found on most systems. File Class Example The File class is extended to illustrate returning objects by reference and by pointer. One extension of the File class is a new method GetStream() that returns a reference to a file stream object (an object in the class fstream), through which the disk file represented by the File object can be manipulated. The second extension to the File class is a change in the View() method that will make it return a pointer to a TextWindow object. The TextWindow pointed to is the window in which the File object is currently displayed. Returning a pointer to this window allows the program to manipulate the window (i.e., to move it or resize it). The two extensions to the File class are shown below.
The design of the GetStream method allows the File class to make available the full functionality of the stream I/O model without duplicating all of the stream I/O methods in its own interface. For example, the following code uses the extended File class to output to a file selected interactively by the user: FileNavigator nav; File aFile = nav.AskUser(); fstream& fileStream = file.GetStream(); fileStream << "add new data to file" << endl; The GetStream method also enables more complicated uses, such as enabling different parts of a system to obtain a reference to the same stream object. This allows different parts of the system to share access to the same underlying disk file. One other advantage of the GetStream method is that the code using the File object need not be aware of the File's name or how the name of the file was obtained. The redefinition of the View method allows the window containing the contents of the disk file to be manipulated under program control. An example of this usage is shown in the following code:
FileNavigator nav;
File aFile = nav.AskUser();
TextWindow* tw = aFile.View(); // present file for viewing, return
// pointer to viewing window
tw->MoveTo(Location(10,10)); // move viewing window
tw->Resize(Shape(200,500)); // resize viewing window
As illustrated in this code, the View method creates a TextWindow and returns a pointer to this TextWindow. The text contents of the file are viewable in this window. The viewing window is then moved and resized by the program. Frame Manager ExampleA simple FrameManager class will be designed to illustrate how objects can be returned by pointer. The FrameManger is responsible for maintaining a collection of Frame objects and returning that Frame object in the collection with a given name. Recall that the construction of a Frame object allows the object to be given a name. It is this name that is used by the FrameManager to identify a given Frame object among all those currently in the FrameManger's collection. Not considered are small details such as how the Manager behaves if there are two Frame objects with the same name. The definition of the FrameManager is shown below. The constructor of the FrameManager allows the maximum number of Frames that the manger will have at any one time to be specified. The default value limits the FrameManager to hold at most ten Frame objects at any one time. The Add and Remove methods are both overloaded to allow the argument to be given by either a pointer or a reference to a Frame object. Also, the Remove method has an additional overloading that allows the Frame object to be identified by its name. The FindByName method returns a pointer to the Frame object whose name matches the input argument of the method; if no such Frame object can be found by the FrameManager, the method returns a null pointer.
An example of using the FrameManager is shown in the skeleton code below. In this code, an application has several windows open simultaneously. Whenever a mouse event occurs in one of these windows, the OnMouseEvent function is called and the name of the Frame is passed as the first argument. Within the OnMouseEvent function, it is usually necessary to operate on the Frame object in which the mouse event occurred. The FrameManager provides an easy way to discover the needed Frame object given its name.
Frame *dialogWindow;
Frame* drawingWindow;
// .. declare pointers to other windows
FrameManager windowManager(5);
void OnStart()
{ dialogWindow = new Frame("Dialogue",...);
drawingWindow = Frame("Drawing,...);
//... instantiate other windows
windowManager.Add(dialogueWindow);
windowManager.Add(drawingWindow);
//... add other windows to Manager
}
void OnMouseEvent (char* frameName, int x, int y, int buttonState)
{ Frame* frame = windowManager.FindByName(frameName));
if (frame != (Frame*)0 // check for null return value
{ // process mouse event in frame using frame->
}
else { // handle case of frame not known to FrameManager
}
}
AssociationsA simple association will be created using a newly defined class, the Message class, and the existing Frame class. The Message class is an abstraction of a displayable text string. A Message object knows what text should be written to a Frame and where within the Frame the text should appear. In addition, a Message object will be responsible for erasing its textfrom the Frame and for updating the Frame when the Message is changed. The definition of the Message class is given in the table below.
Notice that the DisplayIn method passes the parameter object whichFrame by reference; the & symbol following the class name "Frame" indicates the passing by reference. To make the idea of an association concrete, a portion of the Message class's implementation is examined. The private data of the Message class contains two pointers, one for the text string that the Message object displays and one for the Frame object in which the string will be displayed. The Message also contains a Location object indicating where the text string is to appear within the Frame. The private data of the Message class is declared as:
class Message {
private:
//encapsulated implementation
char *msgText; // display this text string
Frame *msgFrame; // in this Frame
Location msgLocation; // at this Location in the Frame
public:
...
};
Avoiding some syntactic details, the code for the DisplayIn method is:
DisplayIn (Frame& whichFrame) {
msgFrame = &whichFrame;
}
This method simply takes the address of the whichFrame object and records that address as the value of the Frame pointer msgFrame. To see the effect of this method, consider the following declarations and code:
// declaration
Frame window("Message Test", Location(100,100), Shape(200,200));
Message greeting("Hello World!", Location(20,20));
// code
greeting.DisplayIn(window);
This code will create an association between the greeting object and the window object that can be pictured as shown in the figure below. This association is created because the greeting object retains a lasting pointer to the window object. This pointer will remain valid until it is changed by the greeting object (i.e., the DisplayIn method is called to have the Message point to a different Frame object) or the greeting object itself is destroyed.
Associations must be managed carefully to avoid the problems of dangling pointers and memory leaks. The example above with the greeting object and the window object can be used to illustrate these two pitfalls. If the window object is deleted, then the greeting object has a dangling pointer because the greeting object still points to the memory space previously occupied by the now destructed window object. A memory leak occurs when the greeting object is deleted but not the window object. In this case, there may be no way to refer to the window object. Such objects continue to occupy memory but are inaccessible. In long-running programs, memory leaks can cause total system failures. The association between the Message object and the Frame object is used by the Clear method in the Message class. The code for this method is:
Clear() { // in Message Class
Shape msgShape = msgFrame->TextSize(msgText);
msgFrame->Clear(msgLocation, msgShape);
}
This code uses the private data members msgFrame and msgText to obtain from the Frame object the shape of the text displayed by the Message object. The Clear method of the Frame class is then used to erase the rectangular area containing the Message object's text. Note that msgText, msgFrame, and msgLocation are private data members that are part of the Message object. These data members are visible to the methods of the Message object and the data members exist as long as the object itself exists (though the value of these data members may change). The Shape object msgShape, however, is a local object of the Clear method; this object exists only during the execution of the Clear method itself. Also notice that both the Message class and the Frame class have a Clear() method. There is no confusion about which Clear method is intended in a given invocation because the class of the object to which the method is applied determines which method is executed. For example, the invocation
greeting.Clear();
invokes the Clear method in the Message class because greeting is an object of the Message class. Similarly, the invocation
window.Clear();
invokes the Clear method in the Frame class because window is an object of the Frame class. A simple association between a Message object and a Frame object is shown in the Hello World example below. In this version, the text "Hello World" can be dragged by the cursor. The Blinking Text example shown in the next section elaborates on the idea of an association.
This version of the Hello World problem is considerably simpler than the one that would result without the Message class. The Message class contains the machinery necessary for a Message object to manage itself more completely. Thus, to move the text on the screen it is only necessary to tell the Message object to move itself - it is not necessary to keep track of this information outside of the Message object.
Tasks
|
|||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||
ÿ