First Review

After finishing most of the requirements to make the string class a concrete data type in the previous chapter, we went back to look at why operator = needs a reference argument rather than a value argument. When we use a value argument, a copy of the argument is made for the use of the called function. In the case of a user-defined data type, this copy is made via the copy constructor defined for that type. If we don't define our own copy constructor, the compiler will generate one for us, which will use memberwise copy; that is, simply copying all of the member variables in the object. While a memberwise copy is fine for simple objects whose data are wholly contained within themselves, it isn't sufficient for objects that contain pointers to data stored in other places, because copying a pointer in one object to a pointer in another object results in the two objects sharing the same actual data. Since our string class does contain such a pointer, the result of this simple(minded) copy is that the newly created string points to the same data as the caller's string. Therefore, when the newly created local string expires at the end of the operator = function, the destructor for that string frees the memory that the caller's string was using to store its data.

This problem is very similar to the reason why we had to write our own operator = in the first place; the compiler-generated version of operator = also simply copies the member variables from the source to the destination object, which causes similar havoc when one of the two "twinned" strings is changed. In the case of our operator =, we can solve the twinning problem by using a reference argument rather than a value argument. A reference argument is another name for the caller's variable rather than a copy of the value in that variable, so no destructor is called for a reference argument when the function exits; therefore, the caller's variable is unchanged.

Next, we examined how it was possible to assign a C string to one of our string variables. This didn't require us to write any more code because we already had a constructor that could create a string from a C string, and an operator = that could assign one string to another one. The compiler helps us out here by employing a rule that can be translated roughly as follows: if we need an object of type A (string, in this case) and we have an object of type B (char*, in this case), and there is a constructor that constructs an A and requires exactly one argument, of type B, then invoke that constructor automatically. The example code is as follows:

n = "My name is Susan";

where n is a string, and "My name is Susan" is a C string literal, whose type is char*. We have an operator = with the declaration:

string& string::operator = (const string& Str);

that takes a string reference argument, and we have a constructor of the form:

string::string(char* p);

that takes a char* argument and creates a new string. So we have a char*, "My name is Susan", and we need a string. Since we have a constructor string::string(char*), the compiler will use that constructor to make a temporary string with the same value as the char*, and then use the assignment operator string::operator = (const string& Str) to assign the value of that temporary string to the string n. The fact that the temporary is created also provides a clue as to why the argument to string::operator = (const string& Str) should be a const reference, rather than just a (non-const) reference, to a string. The temporary string having the value "My name is Susan" created during the execution of the statement n = "My name is Susan "; disappears after operator = is executed, taking with it any changes that operator = might have wanted to apply to the original argument. With a const reference argument, the compiler knows that operator = doesn't wish to change that argument and therefore doesn't give us a warning that we might be changing a temporary value.

At this point, we've taken care of operator =. However, to create a concrete data type, we still have to allow our string variables to be passed as value arguments, which means we need a copy constructor to handle that task. Unfortunately, the compiler-generated copy constructor suffers from the same drawback as the compiler-generated operator =; namely, it copies the pointer to the actual data of the string, rather than copying the data itself. Logically, therefore, the solution to this problem is quite similar to the solution for operator =; we write our own copy constructor that allocates space for the character data to be stored in the newly created string, and then copies the data from the old string to the new string.

However, we still can't use a value argument to our copy constructor, because a value argument needs a copy constructor to make the copy which would cause an infinite regress.[5] This obviously won't work, and will be caught by the compiler. Therefore, as in the case of operator =, we have to use a reference argument; since this is actually just another name for the caller's variable rather than a copy of it, this does not cause an infinite regress. Since we are not going to change the caller's argument, we specify a constant reference argument of type string, or a const string& in C++ terms.

[5] See recursion in the glossary.

At that point in the chapter, we had met the requirements for a concrete data type, but such a type is of limited usefulness as long as we can't get the values displayed on the screen. Therefore, the next order of business was to add a Display member function that takes care of this task. This function isn't particularly complicated, but it does require us to deal with the notion of a C legacy type, the array. Since the compiler treats an array in almost the same way as a pointer, we can use array notation to extract each character that needs to be sent out to the screen. Continuing with our example of the Display function's use, the next topic was a discussion of how chars can be treated as numeric variables.

Then we saw a demonstration of how easy it is to misuse an array so that you destroy data that belong to some other variable. This is an important warning of the dangers of uncontrolled use of pointers and arrays; these are the most error-prone constructs in both C and C++, when not kept under tight rein.

We continued by revisiting the topic of access control and why it is advantageous to keep member variables out of the public section of the class definition. The reasons are similar to the reasons why we should avoid using global variables; it's too hard to keep track of where the value of a public member variable is being referenced, which in turn makes it very difficult to update all the affected areas of the code when changing the class definition. However, it is often useful to allow external functions access to certain information about a class object. We saw how to do this by adding a GetLength member function to our string class.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset