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

Overloading C++ Operators

SpreadsheetCell myCell;

double (SpreadsheetCell::*methodPtr) () const = &SpreadsheetCell::getValue; cout << (myCell.*methodPtr)() << endl;

Note the use of the .* operator to derefence the method pointer and call the method. There is also an equivalent operator->* for calling methods via pointers when you have a pointer to an object instead of the object itself. The operator looks like this:

SpreadsheetCell* myCell = new SpreadsheetCell();

double (SpreadsheetCell::*methodPtr) () const = &SpreadsheetCell::getValue; cout << (myCell->*methodPtr)() << endl;

C++ does not allow you to overload operator.* (just as you can’t overload operator.), but you could overload operator->*. However, it is very tricky, and, given that most C++ programmers don’t even know that you can access methods and members through pointers, it’s probably not worth the trouble. The auto_ptr template in the standard library does not overload operator->*.

Writing Conversion Operators

Going back to the SpreadsheetCell example, consider these two lines of code:

SpreadsheetCell cell1;

string s1 = cell1; // DOES NOT COMPILE!

A SpreadsheetCell contains a string representation, so it seems logical that you could assign it to a string variable. Well, you can’t. The compiler tells you that it doesn’t know how to convert a

SpreadsheetCell to a string. You might be tempted to try forcing the compiler to do what you want like this:

string s1 = (string) cell1; // STILL DOES NOT COMPILE!

First, the preceding code still doesn’t compile because the compiler still doesn’t know how to convert the SpreadsheetCell to a string. It already knew from the first line what you wanted it to do, and it would do it if it could. Second, it’s a bad idea in general to add gratuitous casts to your program. Even if the compiler allowed this cast to compile, it probably wouldn’t do the right thing at run time. For example, it might try to interpret the bits representing your object as a string.

If you want to allow this kind of assignment, you must tell the compiler how to perform it. Specifically, you can write a conversion operator to convert SpreadsheetCells to strings. The prototype looks like this:

class SpreadsheetCell

{

public:

//Omitted for brevity operator string() const;

//Omitted for brevity

};

453

Chapter 16

The name of the function is operator string. It has no return type because the return type is specified by the name of the operator: string. It is const because it doesn’t change the object on which it is called. Yes, it looks odd at first, but you’ll get used to it. The implementation looks like this:

SpreadsheetCell::operator string() const

{

return (mString);

}

That’s all you need to do to write a conversion operator from SpreadsheetCell to string. Now the compiler accepts this line and does the right thing at run time:

SpreadsheetCell cell1;

string s1 = cell1; // Works as expected

You can write conversion operators for any type with this same syntax. For example, here is the prototype for a double conversion operator from SpreadsheetCell:

class SpreadsheetCell

{

public:

//Omitted for brevity operator string() const; operator double() const;

//Omitted for brevity

};

The implementation looks like this:

SpreadsheetCell::operator double() const

{

return (mValue);

}

Now you can write code like the following:

SpreadsheetCell cell1;

double d2 = cell1;

Ambiguity Problems with Conversion Operators

Unfortunately, writing the double conversion operator for the SpreadsheetCell object introduces an ambiguity problem. Consider this line:

SpreadsheetCell cell1;

double d1 = cell1 + 3.3; // DOES NOT COMPILE IF YOU DEFINE operator double()

This line now fails to compile. It worked before you wrote operator double(), so what’s the problem now? The issue is that the compiler doesn’t know if it should convert cell1 to a double with operator double() and perform double addition, or convert 3.3 to a SpreadsheetCell with the double constructor and perform SpreadsheetCell addition. Before you wrote operator double(), the compiler had only one choice: convert 3.3 to a SpreadsheetCell with the double constructor and perform

454

Overloading C++ Operators

SpreadsheetCell addition. However, now the compiler could do either. It doesn’t want to make a choice for you, which you might not like, so it refuses to make any choice at all.

The usual solution to this conundrum is to make the constructor in question explicit, so that the automatic conversion using that constructor is prevented. Unfortunately, we don’t want that constructor to be explicit because we generally like the automatic conversion of doubles to SpreadsheetCells, as explained in Chapter 9. In this case, it’s probably better not to write the double conversion operator for the SpreadsheetCell class.

Conversions for Boolean Expressions

Sometimes it is useful to be able to use objects in Boolean expressions. For example, programmers often use pointers in conditional statements like this:

if (ptr != NULL) {

// Perform some dereferencing action.

}

Sometimes they write shorthand conditions such as:

if (ptr) {

// Perform some dereferencing action.

}

Other times, you see code like the following:

if (!ptr) {

// Do something.

}

Currently, none of the preceding expressions compiles with the Pointer smart pointer class defined earlier. However, you can add a conversion operator to the class to convert it to a pointer type. Then, the comparisons to NULL, as well as the object alone in an if statement, trigger the conversion to the pointer type. The usual pointer type for the conversion operator is void*. Here is the modified Pointer class:

template <typename T> class Pointer

{

public:

Pointer(T* inPtr); ~Pointer();

T& operator*();

const T& operator*() const; T* operator->();

const T* operator->() const;

operator void*() const { return mPtr; } protected:

T* mPtr; private:

Pointer(const Pointer<T>& src);

Pointer<T>& operator=(const Pointer<T>& rhs);

};

Now the following statements all compile and do what you expect:

455

Chapter 16

Pointer<SpreadsheetCell> smartCell(new SpreadsheetCell); smartCell->set(5);

if (smartCell != NULL) { cout << “not NULL!\n”;

}

if (smartCell) {

cout << “not NULL!\n”;

}

if (!smartCell) { cout << “NULL\n”;

}

Another alternative is to overload operator bool instead of operator void*. After all, you’re using the object in a Boolean expression; why not convert it directly to a bool? You could write your Pointer class like this:

template <typename T> class Pointer

{

public:

Pointer(T* inPtr); ~Pointer();

T& operator*();

const T& operator*() const; T* operator->();

const T* operator->() const;

operator bool() const { return (mPtr != NULL); } protected:

T* mPtr; private:

Pointer(const Pointer<T>& src);

Pointer<T>& operator=(const Pointer<T>& rhs);

};

All three of the preceding tests continue to work, though the comparison to NULL explicitly might cause your compiler to generate warnings. This technique seems especially appropriate for objects that don’t represent pointers and for which conversion to a pointer type really doesn’t make sense. Unfortunately, adding a conversion operator to bool presents some unanticipated consequences. C++ applies “promotion” rules to silently convert bool to int whenever the opportunity arises. Therefore, with the preceding conversion operator, such code compiles and runs:

Pointer<SpreadsheetCell> smartCell(new SpreadsheetCell);

int i = smartCell; // Converts smartCell Pointer to bool to int.

That’s usually not behavior that you expect or desire. Thus, many programmers prefer operator void* to operator bool. In fact, recall the following use of streams from Chapter 14:

ifstream istr; int temp;

// Open istr

while (istr >> temp) { // Process temp

}

456