Data Structures and Algorithms I - Lab 7 - String Things

Name ______________________________ Section ______________ Score ________________

Objectives

To use operator overloading with dynamic storage
Learn more about copy constructors
Implement an assignment operator and stream extraction operators
Implement a cast operator

Background

Operator overloading is a powerful technique that can lead to simple and easy to maintain programs. Probably the most fundamental operator that should be overloaded for most classes is the assignment operator. Failure to properly overload this operator is a common cause of failure when dynamic storage is associated with objects. This can occur indirectly when class composition is used. It may also cause corruption of data members that are supposed to contain unique identifiers for each object.

Overloading the assignment operator is especially important since it has a default meaning for all objects and may be applied innocently, with severe consequences. The copy constructor is closely related to the assignment operator. Failure to implement a valid copy constructor can lead to similar failures. The only real difference between copy constructors and assignment operators, is that the assignment operator is given an already constructed object while the constructor starts with newly allocated uninitialized memory.

Experimental Procedure/Analysis

Task 1

Copy the Lab archive, extract the lab files, and open the project. Look at the main program. Several MyString objects are created in main. Build the project. You should get the same error message for two different lines. Record the error message.

___________________________________________________________________________

The lines MyString y(x); is attempting to create a new object from an object named x. The types are the same, so this should invoke the copy constructor. Look at the MyString header file and explain why the error occurred.

___________________________________________________________________________

Correct the error by making the copy constructor public. Keep the assignment operator private. Verify that the program compiles without error.

You should notice that the second compilation error also disappears. This tells us that both "MyString y(x);" and "MyString z = x;" are invoking the copy constructor if a copy constructor is defined.

Task 1a

Change the #define TASK to select task '1a'. Build and run the program. Describe the error that occurs when you run the program.:

___________________________________________________________________________

This error occurs before the nested block is exited. The local variable y, created inside the block, was created using the copy constructor. This copy constructor did not create y properly nor did it copy the contents of x into it; the body contained no statements. The objects are shown below and the sPtr in y is not initialized.

Add a few lines to its body to make sPtr point to a new char array of the proper size with the proper contents! Hint: Use new and strcpy. (You may need to use a temp local variable of type char * as sPtr is const char * and once it points to an array, the array contents may not be modified.) Test the program to ensure it works properly now. Draw a picture that represents memory after y is constructed using your copy constructor.

Task 1b

Change the #define TASK to select task '1b' Build the project. What operator is being invoked on this line? _____________ What error do you get?

___________________________________________________________________________

Look in MyString.h again and comment out the assignment operator. Build to verify that no compilation errors are reported. What does this tell you about the meanings of the statement MyString z = x; versus the pair of statements MyString w; w = x; ?
 

___________________________________________________________________________

Why does the project compile without error - there is a statement that requires an assignment operator, but there is none defined in the program!

___________________________________________________________________________

Task 1c

Change the #define TASK to select task '1c'. Build and run the program. Describe the error:

___________________________________________________________________________

This error occurs after the nested block is exited. The local variable y, created inside the block, was assigned the value of another object, x, using default assignment (memory copy). This copied the data members of x into the data members of y. The objects are shown below, just before the assignment. Change the picture to depict memory after the assignment. You should also circle the storage that has been leaked!

When y leaves scope, its destructor deallocates the array pointed to by its data member, sPtr. Why does the next statement, cout << "x:" << x.str() << endl, fail?

___________________________________________________________________________

Uncomment the assignment operator in MyString.h. You should also make it public. Add a few lines to its body to first delete the old array (sPtr) and then make sPtr point to a new char array of the proper size with the proper contents! Do NOT test for self-assignment! If you get an error on the delete, examine the MyString destructor for a hint on how to get around it! Test the program to ensure it works properly now.

Task 1d

Change the #define TASK to select task '1d'. Build and run the program. Describe the error:

___________________________________________________________________________

Fix the error by testing for and avoiding self-assignment in the assignment operator. Verify that the program runs properly.

Task 1e

Change the #define TASK to select task '1e'. Build and run the program. Describe the error(s):

___________________________________________________________________________

Implement the two operators in MyString.cpp. One is a member, one is a non-member, non-friend (for variety). You must also uncomment two declarations in MyString.h. Verify that the program now runs properly. The bool values true and false will be output as 1 and 0 when you test.

Task 1f

Change the #define TASK to select task '1f'. Build and run the program. Describe the error(s):

___________________________________________________________________________

We want to output MyString objects using the stream insertion operator. The most troublefree way of accomplishing this is to overload the stream insertion operator. Notice that we can output a string by writing cout << aString.str(); We want to avoid the explicit method call. Uncomment the prototype for the stream insertion operator in MyString.h and the implementation in MyString.cpp and retry the program. Verify that it works properly. Explain how the output of a MyString object is now handled by the stream insertion operator.

___________________________________________________________________________

___________________________________________________________________________

Uncomment the cast operator in MyString.h. What information does this operator return? 

___________________________________________________________________________

Use the cast operator to correct the last three lines of output in the section of main labeled as task 1f. Write the changed or added code here:

____________________________________________________________________________

Do you need to use an explicit cast, or does an implicit cast work? (check one of the following)

___ I must use an explicit cast   ___ Both an explicit and an implicit cast works

Task 1g

Change the #define TASK to select task '1g'. Build and run the program. Describe the error(s):

___________________________________________________________________________

We want to be able to input directly into a MyString object. To make this work, we will have to overload the stream extraction operator: operator>>. This operator will be given an istream object (like cin) and an existing MyString object. The extraction operation should skip any leading whitespace in the file, and then read characters into the MyString object until another whitespace character is found. Of course, the old characters of the MyString object are no longer needed, so we should discard them (and the storage they occupy) and allocate a new array of appropriate sze to hold the input data. The significant problem we face is not knowing how long this input might be, so we cannot predict the correct size for the array!

If we allocate an array of size (sPtr = new char[N];), and then extract theFile>>sPtr; we run the risk of storing characters past the end of the array. There are several good solutions to this problem. We will look at only one.

We will start with a small array, large enough for a 2 character string (3 bytes). We will take one character at a time from the input stream (after skipping whitespace), and store it in the array at the next available location, so long as there is still room. If the array becomes full, we will "resize" - doubling the size of the string the array is capable of holding. Then we will copy the characters to the new array, delete the old one, point to the new one, and then continue storing more input characters until we peek ahead and find a whitespace character. We will have to manually store the terminating null byte to make sure we have a valid string.

This function will want access to the private members of MyString, so we should declare it a friend of the MyString class. Its implementation will be placed in MyString.cpp. Uncomment the friend declaration and the function definition. Follow the comments to complete the task. Test the program by typing long and short sequences of words. Be sure all works properly!

You're all done. Hand in this lab. Turn in a printed copy of MyString.h and MyString.cpp with your lab. Be sure your name appears in the printed files!