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

Mastering Classes and Objects

Operator Overloading

You often want to perform operations on objects such as adding them, comparing them, or streaming them to or from files. For example, spreadsheets are really only useful when you can perform arithmetic actions on them such as summing an entire row of cells.

Implementing Addition

In true object-oriented fashion, SpreadsheetCell objects should be able to add themselves to other SpreadsheetCell objects. Adding a cell to another cell produces a third cell with the result. It doesn’t change either of the original cells. The meaning of addition for SpreadsheetCells is the addition of the values of the cells. The string representations are ignored.

First Attempt: The add Method

You can declare and define an add method for your SpreadsheetCell class like this:

class SpreadsheetCell

{

public:

// Omitted for brevity

const SpreadsheetCell add(const SpreadsheetCell& cell) const;

// Omitted for brevity

};

This method adds two cells together, returning a new third cell whose value is the sum of the first two. It is declared const and takes a reference to a const SpreadsheetCell because add() does not change either of the source cells. It returns a const SpreadsheetCell because you don’t want users to change the return value. They should just assign it to another object. add() is a method, so it is called on one object and passed another. Here is the implementation:

const SpreadsheetCell SpreadsheetCell::add(const SpreadsheetCell& cell) const

{

SpreadsheetCell newCell;

newCell.set(mValue + cell.mValue); // call set to update mValue and mString return (newCell);

}

Note that the implementation creates a new SpreadsheetCell called newCell and returns a copy of that cell. That only works because you wrote a copy constructor for this class. You might be tempted to return a reference to the cell instead. However, that will not work because as soon as the add() method ends and newCell goes out of scope it will be destroyed. The reference that you returned will then be a dangling reference.

You can use the add method like this:

SpreadsheetCell myCell(4), anotherCell(5);

SpreadsheetCell aThirdCell = myCell.add(anotherCell);

That works, but it’s a bit clumsy. You can do better.

209

Chapter 9

Second Attempt: Overloaded operator+ as a Method

It would be convenient to be able to add two cells with the plus sign the way that you add two ints or two doubles. Something like this:

SpreadsheetCell myCell(4), anotherCell(5);

SpreadsheetCell aThirdCell = myCell + anotherCell;

Luckily, C++ allows you to write your own version of the plus sign, called the addition operator, to work correctly with your classes. To do that you write a method with the name operator+ that looks like this:

class SpreadsheetCell

{

public:

// Omitted for brevity

const SpreadsheetCell operator+(const SpreadsheetCell& cell) const;

// Omitted for brevity

};

The definition of the method is identical to the implementation of the add() method:

const SpreadsheetCell SpreadsheetCell::operator+(const SpreadsheetCell& cell) const

{

SpreadsheetCell newCell;

newCell.set(mValue + cell.mValue); // Call set to update mValue and mString. return (newCell);

}

Now you can add two cells together using the plus sign as shown previously!

This syntax takes a bit of getting used to. Try not to worry too much about the strange method name operator+ — it’s just a name like foo or add. In order to understand the rest of the syntax, it helps to understand what’s really going on. When your C++ compiler parses a program and encounters an operator, such as +, -, =, or <<, it tries to find a function or method with the name operator+, operator-, operator=, or operator<<, respectively, that takes the appropriate parameters. For example, when the compiler sees the following line, it tries to find either a method in the SpreadsheetCell class named operator+ that takes another SpreadsheetCell object or a global function named operator+ that takes two SpreadsheetCell objects:

SpreadsheetCell aThirdCell = myCell + anotherCell;

Note that there’s no requirement that operator+ take as a parameter an object of the same type as the class for which it’s written. You could write an operator+ for SpreadsheetCells that takes a Spreadsheet to add to the SpreadsheetCell. That wouldn’t make sense to the programmer, but the compiler would allow it.

Note also that you can give operator+ any return value you want. Operator overloading is a form of function overloading, and recall that function overloading does not look at the return type of the function.

210

Mastering Classes and Objects

Implicit Conversions

Surprisingly, once you’ve written the operator+ shown earlier, not only can you add two cells together, you can also add a cell to a string, a double, or an int!

SpreadsheetCell myCell(4), aThirdCell; string str = “hello”;

aThirdCell = myCell + str; aThirdCell = myCell + 5.6; aThirdCell = myCell + 4;

The reason this code works is that the compiler does more to try to find an appropriate operator+ than just look for one with the exact types specified. The compiler also tries to find an appropriate conversion for the types so that an operator+ can be found. Constructors that take the type in question are appropriate converters. In the preceding example, when the compiler sees a SpreadsheetCell trying to add itself to double, it finds the SpreadsheetCell constructor that takes a double and constructs a temporary SpreadsheetCell object to pass to operator+. Similarly, when the compiler sees the line trying to add a SpreadsheetCell to a string, it calls the string SpreadsheetCell constructor to create a temporary SpreadsheetCell to pass to operator+.

This implicit conversion behavior is usually convenient. However, in the preceding example, it doesn’t really make sense to add a SpreadsheetCell to a string. You can prevent the implicit construction of a SpreadsheetCell from a string by marking that constructor with the explicit keyword:

class SpreadsheetCell

{

public:

SpreadsheetCell(); SpreadsheetCell(double initialValue);

explicit SpreadsheetCell(const string& initialValue); SpreadsheetCell(const SpreadsheetCell& src); SpreadsheetCell& operator=(const SpreadsheetCell& rhs);

// Remainder omitted for brevity

};

The explicit keyword goes only in the class definition, and only makes sense when applied to constructors with exactly one argument.

Third Attempt: Global Operator+

Implicit conversions allow you to use an operator+ method to add your SpreadsheetCell objects to ints and doubles. However, the operator is not commutative, as shown in the following code:

aThirdCell = myCell + 4; // Works fine. aThirdCell = myCell + 5.6; // Works fine.

aThirdCell = 4 + myCell; // FAILS TO COMPILE! aThirdCell = 5.6 + myCell; // FAILS TO COMPILE!

The implicit conversion works fine when the SpreadsheetCell object is on the left of the operator, but doesn’t work when it’s on the right. Addition is supposed to be commutative, so something is wrong here. The problem is that the operator+ method must be called on a SpreadsheetCell object, and that

211

Chapter 9

object must be on the left-hand side of the operator+. That’s just the way the C++ language is defined. So, there’s no way you can get the above code to work with an operator+ method.

However, you can get it to work if you replace the in-class operator+ with a global operator+ function that is not tied to any particular object. The function looks like this:

const SpreadsheetCell operator+(const SpreadsheetCell& lhs, const SpreadsheetCell& rhs)

{

SpreadsheetCell newCell;

newCell.set(lhs.mValue + rhs.mValue); // Call set to update mValue and mString. return (newCell);

}

Now all four of the addition lines work as you expect:

aThirdCell = myCell + 4; // Works fine. aThirdCell = myCell + 5.6; // Works fine.

aThirdCell = 4 + myCell; // Works fine. aThirdCell = 5.6 + myCell; // Works fine.

Note that the implementation of the global operator+ accesses protected data members of SpreadsheetCell objects. Therefore, it must be a friend function of the SpreadsheetCell class:

class SpreadsheetCell

{

public:

// Omitted for brevity

friend const SpreadsheetCell operator+(const SpreadsheetCell& lhs,

const SpreadsheetCell& rhs);

//Omitted for brevity

};

You might be wondering what happens if you write the following code:

aThirdCell = 4.5 + 5.5;

It compiles and runs, but it’s not calling the operator+ you wrote. It does normal double addition of 4.5 and 5.5, and then constructs a temporary SpreadsheetCell object with the double constructor, which it assigns to aThirdCell.

Third time’s the charm. A global operator+ is the best you can do in C++.

Overloading Arithmetic Operators

Now that you understand how to write operator+, the rest of the basic arithmetic operators are straightforward. Here are declarations of -, *, and / (you can also overload %, but it doesn’t make sense for the double values stored in SpreadsheetCells):

212

Mastering Classes and Objects

class SpreadsheetCell

{

public:

// Omitted for brevity

friend const SpreadsheetCell operator+(const SpreadsheetCell& lhs, const SpreadsheetCell& rhs);

friend const SpreadsheetCell operator-(const SpreadsheetCell& lhs, const SpreadsheetCell& rhs);

friend const SpreadsheetCell operator*(const SpreadsheetCell& lhs,

const SpreadsheetCell& rhs);

friend const SpreadsheetCell operator/(const SpreadsheetCell& lhs, const SpreadsheetCell& rhs);

// Omitted for brevity

};

Here are the implementations. The only tricky aspect is remembering to check for division by 0. Although not mathematically correct, this implementation sets the result to 0 if division by zero is detected:

const SpreadsheetCell operator-(const SpreadsheetCell& lhs, const SpreadsheetCell& rhs)

{

SpreadsheetCell newCell;

newCell.set(lhs.mValue - rhs.mValue); // Call set to update mValue and mString. return (newCell);

}

const SpreadsheetCell operator*(const SpreadsheetCell& lhs, const SpreadsheetCell& rhs)

{

SpreadsheetCell newCell;

newCell.set(lhs.mValue * rhs.mValue); // Call set to update mValue and mString. return (newCell);

}

const SpreadsheetCell operator/(const SpreadsheetCell& lhs, const SpreadsheetCell& rhs)

{

SpreadsheetCell newCell; if (rhs.mValue == 0) {

newCell.set(0); // Call set to update mValue and mString. } else {

newCell.set(lhs.mValue / rhs.mValue); // Call set to update mValue // and mString.

}

return (newCell);

}

C++ does not require you to actually implement multiplication in operator*, division in operator/, and so on. You could implement multiplication in operator/, division in operator+, and so forth. However, that would be extremely confusing, and there is no good reason to do so except as a practical joke. Whenever possible, stick to the commonly used operator meanings in your implementations.

213

Chapter 9

Overloading the Arithmetic Shorthand Operators

In addition to the basic arithmetic operators, C++ provides shorthand operators such as += and -=. You might assume that writing operator+ for your class provides operator+= also. No such luck. You have to overload the shorthand arithmetic operators explicitly. These operators differ from the basic arithmetic operators in that they change the object on the left-hand side of the operator instead of creating a new object. A second, subtler, difference is that, like the assignment operator, they generate a result that is a reference to the modified object.

The arithmetic operators always require an object on the left-hand side, so you should write them as methods, not as global functions. Here are the declarations for the SpreadsheetCell class:

class SpreadsheetCell

{

public:

// Omitted for brevity

friend const SpreadsheetCell operator+(const SpreadsheetCell& lhs, const SpreadsheetCell& rhs);

friend const SpreadsheetCell operator-(const SpreadsheetCell& lhs, const SpreadsheetCell& rhs);

friend const SpreadsheetCell operator*(const SpreadsheetCell& lhs, const SpreadsheetCell& rhs);

friend const SpreadsheetCell operator/(const SpreadsheetCell& lhs, const SpreadsheetCell& rhs);

SpreadsheetCell& operator+=(const SpreadsheetCell& rhs); SpreadsheetCell& operator-=(const SpreadsheetCell& rhs);

SpreadsheetCell& operator*=(const SpreadsheetCell& rhs);

SpreadsheetCell& operator/=(const SpreadsheetCell& rhs);

// Omitted for brevity

};

Here are the implementations:

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

{

set(mValue + rhs.mValue); // Call set to update mValue and mString. return (*this);

}

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

{

set(mValue - rhs.mValue); // Call set to update mValue and mString. return (*this);

}

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

{

set(mValue * rhs.mValue); // Call set to update mValue and mString. return (*this);

}

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

{

214