More Stringbad Problems: Assignment Operators

Not all the problems in Listing 12.3 can be blamed on the default copy constructor; you have to look at the default assignment operator, too. Just as ANSI C allows structure assignment, C++ allows class object assignment. It does so by automatically overloading an assignment operator for a class. This operator has the following prototype:

Class_name & Class_name::operator=(const Class_name &);

That is, it takes and returns a reference to an object of the class. For example, here’s the prototype for the StringBad class:

StringBad & StringBad::operator=(const StringBad &);

When an Assignment Operator Is Used and What It Does

An overloaded assignment operator is used when you assign one object to another existing object:

StringBad headline1("Celery Stalks at Midnight");
...
StringBad knot;
knot = headline1;   // assignment operator invoked

An assignment operator is not necessarily used when initializing an object:

StringBad metoo = knot; // use copy constructor, possibly assignment, too

Here metoo is a newly created object being initialized to knot’s values; hence, the copy constructor is used. However, as mentioned before, implementations have the option of handling this statement in two steps: using the copy constructor to create a temporary object and then using assignment to copy the values to the new object. That is, initialization always invokes a copy constructor, and forms using the = operator may also invoke an assignment operator.

Like a copy constructor, an implicit implementation of an assignment operator performs a member-to-member copy. If a member is itself an object of some class, the program uses the assignment operator defined for that class to do the copying for that particular member. Static data members are unaffected.

Where Stringbad Assignment Goes Wrong

Listing 12.3 assigns headline1 to knot:

knot = headline1;

When the destructor is called for knot, it displays this message:

"Celery Stalks at Midnight" object deleted, 2 left

When the destructor is called for headline1, it displays this message:

"-|" object deleted, -2 left

(Many implementations abort before getting this far.)

Here you see the same problem that the implicit copy constructor caused: corrupted data. Once again, the problem is memberwise copying, which causes both headline1.str and knot.str to point to the same address. Thus, when the destructor is called for knot, it deletes the string "Celery Stalks at Midnight", and when it’s called for headline1, it attempts to delete the previously deleted string. As mentioned earlier, the effect of attempting to delete previously deleted data is undefined, so it may change the memory contents, and it may cause a program to abort. As some like to point out, if the effect of a particular operation is undefined, your compiler can do anything it wants, including displaying the Declaration of Independence or freeing your hard disk of unsightly files. Of course, it would be a rare thing for a compiler writer to take the time to include such frivolous or destructive behavior.

Fixing Assignment

The solution for the problems created by an inappropriate default assignment operator is to provide your own assignment operator definition, one that makes a deep copy. The implementation is similar to that of the copy constructor, but there are some differences:

• Because the target object may already refer to previously allocated data, the function should use delete [] to free former obligations.

• The function should protect against assigning an object to itself; otherwise, the freeing of memory described previously could erase the object’s contents before they are reassigned.

• The function returns a reference to the invoking object.

By returning an object, the function can emulate the way ordinary assignment for built-in types can be chained. That is, if S0, S1, and S2 are StringBad objects, you can write the following:

S0 = S1 = S2;

In function notation, this becomes the following:

S0.operator=(S1.operator=(S2));

Thus, the return value of S1.operator=(S2) becomes the argument of the S0.operator=() function. Because the return value is a reference to a String object, it is the correct argument type.

Here’s how you could write an assignment operator for the StringBad class:

StringBad & StringBad::operator=(const StringBad & st)
{
    if (this == &st)           // object assigned to itself
        return *this;          // all done
    delete [] str;             // free old string
    len = st.len;
    str = new char [len + 1];  // get space for new string
    std::strcpy(str, st.str);  // copy the string
    return *this;              // return reference to invoking object
}

First, the code checks for self-assignment. It does so by seeing if the address of the right-hand side of the assignment (&s) is the same as the address of the receiving object (this). If so, the program returns *this and terminates. You may recall from Chapter 10 that the assignment operator is one of the operators that can be overloaded only by a class member function.

Otherwise, the function proceeds to free the memory that str pointed to. The reason for this is that shortly thereafter str will be assigned the address of a new string. If you don’t first apply the delete operator, the previous string will remain in memory. Because the program no longer has a pointer to the old string, that memory will be wasted.

Next, the program proceeds like a copy constructor, allocating enough space for the new string and then copying the string from the right-hand object to the new location.

When it is finished, the program returns *this and terminates.

Assignment does not create a new object, so you don’t have to adjust the value of the static data member num_strings.

Adding the copy constructor and the assignment operator described previously to the StringBad class clears up all the problems. Here, for example, are the last few lines of output after these changes have been made:

End of main()
"Celery Stalks at Midnight" object deleted, 4 left
"Spinach Leaves Bowl for Dollars" object deleted, 3 left
"Spinach Leaves Bowl for Dollars" object deleted, 2 left
"Lettuce Prey" object deleted, 1 left
"Celery Stalks at Midnight" object deleted, 0 left

The object counting is correct now, and none of the strings have been mangled.

The New, Improved String Class

Now that we are a bit wiser, we can revise the StringBad class, renaming it String. First, add the copy constructor and the assignment operator just discussed so that the class correctly manages the memory used by class objects. Next, now that you’ve seen when objects are constructed and destroyed, we can mute the class constructors and destructors so that they no longer announce each time they are used. Also now that you’re no longer watching the constructors at work, we can simplify the default constructor so that it constructs an empty string instead of "C++".

Next, we can add a few capabilities to the class. A useful String class would incorporate all the functionality of the standard cstring library of string functions, but we’ll add only enough to see what happens. (Keep in mind that this String class is an illustrative example and that the C++ standard string class is much more extensive.) In particular, we’ll add the following methods:

int length () const { return len; }
friend bool operator<(const String &st, const String &st2);
friend bool operator>(const String &st1, const String &st2);
friend bool operator==(const String &st, const String &st2);
friend operator>>(istream & is, String & st);
char & operator[](int i);
const char & operator[](int i) const;
static int HowMany();

The first new method returns the length of the stored string. The next three friend functions allow you to compare strings. The operator>>() function provides simple input capabilities. The two operator[]() functions provide array-notation access to individual characters in a string. The static class method HowMany() complements the static class data member num_strings. Let’s look at some details.

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

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