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

Chapter 23

A New Assignment Operator, Should You Decide to Accept It

In This Chapter

Introduction to the assignment operator

Why and when the assignment operator is necessary

Similarities between the assignment operator and the copy constructor

The intrinsic data types are those that are built in the language, such as int, float, double, and so on, plus the various pointer types. Chapter 3

and Chapter 4 describe the operators that C++ defines for the intrinsic data types. C++ enables the programmer to define the operators for classes that the programmer has created in addition to these intrinsic operators. This is called operator overloading.

Normally, operator overloading is optional and not attempted by beginning C++ programmers. A lot of experienced C++ programmers (including me) don’t think operator overloading is such a great idea either. However, you must figure out how to overload one operator: the assignment operator.

Comparing Operators with Functions

An operator is nothing more than a built-in function with a peculiar syntax. The following addition

a + b

could be understood as though it were written

operator+(a, b)

306 Part V: Optional Features

C++ gives each operator a function-style name. The functional name of an operator is the operator symbol preceded by the keyword operator and fol­ lowed by the appropriate argument types. For example, the + operator that adds an int to an int generating an int is called int operator+(int, int).

Any operator can be defined for a user-defined class. Thus, I could create a

Complex operator*(Complex&, Complex&) that would allow me to multi­ ply two objects of type Complex. The new operator may have the same semantics as the operator it overloads, but it doesn’t have to. The following rules apply when overloading operators:

The programmer cannot overload the ., ::, * (dereference), and & operators.

The programmer cannot invent new operators. You cannot invent the operation x $ y.

The format of the operators cannot be changed. Thus, you cannot define an operation %i because % is a binary operator.

The operator precedence cannot change. A program cannot force operator+ to be evaluated before operator*.

The operators cannot be redefined when applied to intrinsic types — you can’t change the meaning of 1 + 2. Existing operators can be over­ loaded only for newly defined types.

Overloading operators is one of those things that seems like a much better idea than it really is. In my experience, operator overloading introduces more problems than it solves, with two notable exceptions that are the subject of this chapter.

Inserting a New Operator

The insertion and extraction operators << and >> are nothing more than the left and right shift operators overloaded for a set of input/output classes. These definitions are found in the include file iostream (which is why every program includes that file). Thus, cout << “some string” becomes operator<<(cout, “some string”). Our old friends cout and cin are predefined objects that are tied to the console and keyboard, respectively.

I discuss this relationship in Chapter 24.

Chapter 23: A New Assignment Operator, Should You Decide to Accept It 307

Creating Shallow Copies

Is a Deep Problem

No matter what anyone may think of operator overloading, you will need to overload the assignment operator for many classes that you generate. C++ provides a default definition for operator=() for all classes. This default def­ inition performs a member-by-member copy. This works great for an intrinsic type like an int.

int i;

i = 10; // “member by member” copy

This same default definition is applied to user-defined classes. In the follow­ ing example, each member of source is copied over the corresponding member in destination.

void fn()

{

MyStruct source, destination; destination = source;

}

The default assignment operator works for most classes; however, it is not correct for classes that allocate resources, such as heap memory. The pro­ grammer must overload operator=() to handle the transfer of resources.

The assignment operator is much like the copy constructor. In use, the two look almost identical:

void fn(MyClass &mc)

 

{

 

MyClass newMC(mc);

// of course, this uses the

 

// copy constructor

MyClass newerMC = mc;// less obvious, this also invokes

 

// the copy constructor

MyClass newestMC;

// this creates a default object

newestMC = mc;

// and then overwrites it with

}

// the argument passed

 

 

 

The creation of newMC follows the standard pattern of creating a new object as a mirror image of the original using the copy constructor MyClass(MyClass&). Not so obvious is that newerMC is also created using the copy constructor.

308 Part V: Optional Features

MyClass a = b is just another way of writing MyClass a(b) — in particular, this declaration does not involve the assignment operator despite its appear­ ance. However, newestMC is created using the default (void) constructor and then overwritten by mc using the assignment operator.

Like the copy constructor, an assignment operator should be provided when­ ever a shallow copy is not appropriate. (Chapter 18 discusses shallow versus deep constructors.) A simple rule is to provide an assignment operator for classes that have a user-defined copy constructor.

The rule is this: The copy constructor is used when a new object is being cre­ ated. The assignment operator is used if the left-hand object already exists.

Overloading the Assignment Operator

The DemoAssignmentOperator program demonstrates how to provide an assignment operator. The program also includes a copy constructor to pro­ vide a comparison.

//DemoAssignmentOperator - demonstrate the assignment

// operator on a user defined class #include <cstdio>

#include <cstdlib> #include <iostream> #include <string> using namespace std;

//Name - a generic class used to demonstrate

//the assignment and copy constructor

//operators

class Name

{

public:

Name(char *pszN = 0)

{

copyName(pszN, “”);

}

Name(Name& s)

{

copyName(s.pszName, “ (copy)”);

}

~Name()

{

deleteName();

}

//assignment operator Name& operator=(Name& s)

{

Chapter 23: A New Assignment Operator, Should You Decide to Accept It 309

//delete existing stuff...

deleteName();

//...before replacing with new stuff copyName(s.pszName, “ (replaced)”); //return reference to existing object return *this;

}

// very simple access function char* out() { return pszName; }

protected:

void copyName(char* pszN, char* pszAdd); void deleteName();

char *pszName;

};

//copyName() - allocate heap memory to store name void Name::copyName(char* pszN, char* pszAdd)

{

pszName = 0; if (pszN)

{

pszName = new char[strlen(pszN) + strlen(pszAdd) + 1];

strcpy(pszName, pszN); strcat(pszName, pszAdd);

}

}

//deleteName() - return heap memory void Name::deleteName()

{

if (pszName)

{

delete pszName; pszName = 0;

}

}

int main(int nNumberofArgs, char* pszArgs[])

{

//create two objects Name n1(“Claudette”); Name n2(“Greg”);

cout << n1.out() << “ and “

<<n2.out() << “ are newly created objects”

<<endl;

//now make a copy of an object

Name n3(n1);

cout << n3.out() << “ is a copy of “ << n1.out() << endl;

310 Part V: Optional Features

//create a new object using the “=” format

//for accessing the copy constructor

Name n4 = n1;

cout << n4.out() << “ is also a copy of “

<<n1.out() << endl;

//overwrite n2 with n1 n2 = n1;

cout << n1.out() << “ was assigned to “

<<n2.out() << endl;

//wait until user is ready before terminating program

//to allow the user to see the program results system(“PAUSE”);

return 0;

}

The class Name contains a pointer to a person’s name, which it allocates from the heap in the constructor. The constructors and destructor for class Name are similar to those presented in Chapters 17 and 18. The constructor Name(char*) copies the name given it to the pszName data member. This constructor also serves as the default constructor. The copy constructor Name(&Name) copies the name of the object passed to the name stored in the current object by calling copyName(). The destructor returns the pszName character string to the heap by calling deleteName().

The assignment operator=() is a method of the class. It looks to all the world like a destructor immediately followed by a copy constructor. This is typical. Consider the assignment in the example n2 = n1. The object n2 already has a name associated with it (“Greg”). In the assignment, the memory that the orig­ inal name occupies must be returned to the heap by calling deleteName(), just like a destructor. The assignment operator then invokes copyName() to copy the new information into the object, much like a copy constructor.

The copy constructor did not need to call deleteName() because the object didn’t already exist. Therefore, memory had not already been assigned to the object when the constructor was invoked. The destructor didn’t perform the copy function.

There are two more details about the assignment operator. First, the return type of operator=() is Name&. Expressions involving the assignment opera­ tor have a value and a type, both of which are taken from the final value of the left-hand argument. In the following example, the value of operator=() is 2.0, and the type is double.

double d1, d2; void fn(double );

d1 = 2.0; // the value of this expression is 2.0