Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Professional C++ [eng].pdf
Скачиваний:
1712
Добавлен:
16.08.2013
Размер:
11.09 Mб
Скачать

Chapter 8

 

. . . then the compiler

. . . and you can

 

If you define . . .

generates . . .

create an object . . .

Example

 

 

 

 

A copy constructor

No constructors

Theoretically, as a copy

No example.

only

 

of another object.

 

 

 

Practically, you can’t

 

 

 

create any objects.

 

A single-argument

A copy constructor

With arguments.

SpreadsheetCell

(noncopy constructor)

 

As a copy of another

cell(6);

or multiargument

 

object.

SpreadsheetCell

constructor only

 

 

myCell(cell);

A 0-argument

A copy constructor

With no arguments.

SpreadsheetCell

constructor as well

 

With arguments.

cell;

as a single-argument

 

As a copy of another

SpreadsheetCell

(noncopy constructor)

 

object.

myCell(5);

or multiargument

 

 

SpreadsheetCell

constructor

 

 

anotherCell(cell);

 

 

 

 

Note the lack of symmetry between the default constructor and the copy constructor. As long as you don’t define a copy constructor explicitly, the compiler creates one for you. On the other hand, as soon as you define any constructor, the compiler stops generating a default constructor.

Object Destruction

When an object is destroyed, two events occur: the object’s destructor method is called, and the memory it was taking up is freed. The destructor is your chance to perform any cleanup work for the object, such as freeing dynamically allocated memory or closing file handles. If you don’t declare a destructor, the compiler will write one for you that does recursive memberwise destruction and allows the object to be deleted. The section on dynamic memory allocation in Chapter 9 shows you how to write a destructor.

Objects on the stack are destroyed when they go out of scope, which means whenever the current function, method, or other execution block ends. In other words, whenever the code encounters an ending curly brace, any objects created on the stack within those curly braces are destroyed. The following program shows this behavior:

int main(int argc, char** argv)

{

SpreadsheetCell myCell(5);

if (myCell.getValue() == 5) { SpreadsheetCell anotherCell(6);

} // anotherCell is destroyed as this block ends.

cout << “myCell: “ << myCell.getValue() << endl;

return (0);

} // myCell is destroyed as this block ends.

176

Gaining Proficiency with Classes and Objects

Objects on the stack are destroyed in the reverse order of their declaration (and construction). For example, in the following code fragment, myCell2 is allocated before anotherCell2, so anotherCell2 is destroyed before myCell2 (note that you can start a new code block at any point in your program with an opening curly brace):

{

SpreadsheetCell myCell2(4);

SpreadsheetCell anotherCell2(5); // myCell2 constructed before anotherCell2 } // anotherCell2 destroyed before myCell2

This ordering applies to objects that are data members of other objects. Recall that data members are initialized in the order of their declaration in the class. Thus, following the rule that objects are destroyed in the reverse order of their construction, data member objects are destroyed in the reverse order of their declaration in the class.

Objects allocated on the heap are not destroyed automatically. You must call delete on the object pointer to call its destructor and free the memory. The following program shows this behavior:

int main(int argc, char** argv)

{

SpreadsheetCell* cellPtr1 = new SpreadsheetCell(5); SpreadsheetCell* cellPtr2 = new SpreadsheetCell(6);

cout << “cellPtr1: “ << cellPtr1->getValue() << endl;

delete cellPtr1; // Destroys cellPtr1

return (0);

} // cellPtr2 is NOT destroyed because delete was not called on it.

Assigning to Objects

Just as you can assign the value of one int to another in C++, you can assign the value of one object to another. For example, the following code assigns the value of myCell to anotherCell:

SpreadsheetCell myCell(5), anotherCell;

anotherCell = myCell;

You might be tempted to say that myCell is “copied” to anotherCell. However, in the world of C++, “copying” only occurs when an object is being initialized. If an object already has a value that is being overwritten, the more accurate term is “assigned” to. Note that the facility that C++ provides for copying is the copy constructor. Since it is a constructor, it can only be used for object creation, not for later assignments to the object.

Therefore, C++ provides another method in every class to perform assignment. This method is called the assignment operator. Its name is operator= because it is actually an overloading of the = operator for that class. In the above example, the assignment operator for anotherCell is called, with myCell as the argument.

177

Chapter 8

As usual, if you don’t write your own assignment operator, C++ writes one for you to allow objects to be assigned to one another. The default C++ assignment behavior is almost identical to its default copying behavior: it recursively assigns each data member from the source to the destination object. The syntax is slightly tricky, though.

Declaring an Assignment Operator

Here is another attempt at the SpreadsheetCell class definition, this time including an assignment operator:

class SpreadsheetCell

{

public:

SpreadsheetCell(); SpreadsheetCell(double initialValue);

SpreadsheetCell(const string& initialValue); SpreadsheetCell(const SpreadsheetCell &src); SpreadsheetCell& operator=(const SpreadsheetCell& rhs); void setValue(double inValue);

double getValue();

void setString(const string& inString); string getString();

protected:

string doubleToString(double inValue);

double stringToDouble(const string& inString);

double mValue; string mString;

};

The assignment operator, like the copy constructor, takes a const reference to the source object. In this case, we call the source object rhs, which stands for “right-hand side” of the equals sign. The object on which the assignment operator is called is the left-hand side of the equals sign.

Unlike a copy constructor, the assignment operator returns a reference to a SpreadsheetCell object. The reason is that assignments can be chained, as in the following example:

myCell = anotherCell = aThirdCell;

When that line is executed, the first thing that happens is that the assignment operator for anotherCell is called with aThirdCell as its “right-hand side” parameter. Next, the assignment operator for myCell is called. However, its parameter is not anotherCell. Its right-hand side is the result of the assignment of aThirdCell to anotherCell. If that assignment fails to return a result, there is nothing to pass to myCell!

You might be wondering why the assignment operator for myCell can’t just take anotherCell. The reason is that using the equals sign is actually just shorthand for what is really a method call. When you look at the line in its full functional syntax, you can see the problem:

myCell.operator=(anotherCell.operator=(aThirdCell));

178

Gaining Proficiency with Classes and Objects

Now, you can see that the operator= call from anotherCell must return a value, which is passed to the operator= call for myCell. The correct value to return is anotherCell itself, so it can serve as the source for the assignment to myCell. However, returning anotherCell directly would be inefficient, so you can return a reference to anotherCell.

You could actually declare the assignment operator to return whatever type you wanted, including void. However, you should always return a reference to the object on which it is called because that’s what clients expect.

Defining an Assignment Operator

The implementation of the assignment operator is similar to that of a copy constructor, with several important differences. First, a copy constructor is called only for initialization, so the destination object does not yet have valid values. An assignment operator can overwrite the current values in an object. This consideration doesn’t really come into play until you have dynamically allocated memory in your objects. See Chapter 10 for details.

Second, it’s legal in C++ to assign an object to itself. For example, the following code compiles and runs:

SpreadsheetCell cell(4);

cell = cell; // Self-assignment

Your assignment operator shouldn’t prohibit self-assignment, but also shouldn’t perform a full assignment if it happens. Thus, assignment operators should check for self-assignment at the beginning of the method and return immediately.

Here is the definition of the assignment operator for the SpreadsheetCell class:

SpreadsheetCell& SpreadsheetCell::operator=(const SpreadsheetCell& rhs)

{

if (this == &rhs) {

The previous line checks for self-assignment, but is a bit cryptic. Self-assignment occurs when the lefthand side and the right-hand side of the equals sign are the same. One way to tell if two objects are the same is if they occupy the same memory location — more explicitly, if pointers to them are equal. Recall that this is a pointer to an object accessible from any method called on the object. Thus, this is a pointer to the left-hand side object. Similarly, &rhs is a pointer to the right-hand-side object. If these pointers are equal, the assignment must be self-assignment.

return (*this);

}

this is a pointer to the object on which the method executes, so *this is the object itself. The compiler will return a reference to the object to match the declared return value.

mValue = rhs.mValue;

mString = rhs.mString;

179