3.4 Communicating Objects by Reference and By Pointer




There are three major structures that are built using object communicated by reference or by pointer. These structures are:

Each of these uses are explored.

The difference between communicating objects by reference or by pointer is largely syntactic, they each accomplish the important goal of passing original, not copied, objects. In C++, the symbol "&" is used to denote communicating an object by reference and the symbol "*" is used to denote communicating an object by pointer. Confusingly, these two symbols have several meanings in C++ depending on the context in which they are used. To indicate by reference and 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 later use will be illustrated below.

A simple information retrieval example will begiven to illustrate 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. For example:

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:


	class Query {
	 private:
				// encapsulated implementation
	 public:

	  Query (char* searchText);
	  Query();
	  void   SetSearch(char* searchText);
	  char*  GetSearch();
	  void   AskUser();
	  void   SetResult(char* resultText);
	  char*  GetResult();
	  ~Query();
	};


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 undefned. The AskUser method initiates a dialog 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");
        query3 = 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. This table is read as follows. The first line of the table indicates that:

The first row corresponds to the first use of Use Of The SearchFor MethodFor using the query1 object in the example code above. The other rows in the table are similar.

By Reference and By Pointer:
Matching the Sender and Receiver
Receiver's
Declaration
Sender's
Declaration
Actual
Parameter
Sender
Access
Receiver
Access
T& y T x x x.f() y.f()
T* y T x &x x.f() y->f()
T* y T *x x x->f() y->f()
T& y T *x *x x->f() y.f()

Forming Associations

A 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.

The Message Class


class Message {
  private:
		//encapsulated implementation
  public:
	         
      Message (char *textString, Location whereAt);
void  DisplayIn (Frame&   whichFrame);
void  DisplayAt (Location newLocation);
void  ChangeMessage (char* newText);
void  Clear();
void  Draw ();
     ~Message ();
};

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 assocation concrete, a portion of the Message class' implementation is examined. The private data of the Message class contains two pointers, one for the text string that the Message object dispalys 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 assocation 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.

A Simple Association

Associations must be managed carefully to avoid the problems of "dangling pointer" and "memory leaks". The example above with the greeting object and the window object can be used to illustrate these two problems. 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->GetExtent(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 exist (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 more complete example of using an association between a Message object and a Frame object is the Blinking Text example shown below.

The Blinking Text Revised
Frame window("Message Test", Location(100,100), Shape(200,200));
Message greeting("Hello World!", Location(20,20));
int onoff;

void OnStart() {
     greeting.DisplayIn(window);
     greeting.Draw();
     onoff = 1;
};

void OnTimerEvent() {
      if (onoff) { greeting.Clear(); onoff = 0; }
      else       { greeting.Draw();  onoff = 1; }
}

void OnPaint() {
     if (onoff) greeting.Draw(); 
}          

This version of the Blinking Text 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 erase the text from the screen it is only necessary to tell the Message object to Clear itself - it is not necessary to manipulate the Frame object directly.

Managers

A method may return as its value a result object that is returned by reference or by pointer. A common reason for returning objects in this way is to create a manager object that is responsible for creating and distributing objects of another class. Manager objects are able to simplify a system by concealing the design decisions surrounding the construction, duplication, and distribution of the managed objects. Other parts of the system are able to request the object without concern for the low-level management details.

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 so that this method returns 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 in the figure below.

Extended File Class

   class File {
    private:

    public:
      ...
       fstream&    GetStream(); // return stream for this file
       TextWindow* View();	// return pointer to window for this file
      ...
   };

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 extendded 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. For example, different parts of a system could each use the GetStream method 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 ware 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 example:


   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

In this code the View method returns a pointer to the TextWindow that is created by the View method. The text contents of the file are viewable in this window. The viewing window is then moved and resized by the program.



Next Stop


Exercises

  1. Alternating Text: Write a program that implements a simple association using the Message and Frame classes so that the words "Hello" and "World" blink alternately. That is, at any one time, only one of the words is visible.

  2. Use the Query class to implement a small information retrieval system that: (1)uses a FileNavigator to get file to search from the user, (2)gets the search string from the user, and (3) displays in a window the query, the file name, and the result of the query.

  3. Revise the Shrinking Window program so that the window contains two Message objects that display the current height and width of the window. The window should also contain a Message with your full name.

  4. Revise the Border Walk program so that the window contains two Messages that display the current location of the window. The window should also contain a Message with your full name.

  5. Write a Falling Text program in which a text string moves from the top of the display to the bottom of the display. On reaching the bottom of the display, the text string should reappear at the top of the display.

Last Updated: August 20, 1996 / kafura@cs.vt.edu