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

Overloading C++ Operators

An object of a class with a function call operator is called a function object, or functor for short.

At first, the function call operator probably seems a little strange. Why would you want to write a special method for a class to make objects of the class look like function pointers? Why wouldn’t you just write a function or a standard method of a class? The advantage of function objects over standard methods of objects is simple: these objects can sometimes masquerade as function pointers. You can pass function objects as callback functions to routines that expect function pointers, as long as the function pointer types are templatized. See Chapter 22 for details.

The advantages of function objects over global functions are more intricate. There are two main benefits:

Objects can retain information in their data members between repeated calls to their functioncall operators. For example, a function object might be used to keep a running sum of numbers collected from each call to the function-call operator.

You can customize the behavior of a function object by setting data members. For example, you could write a function object to compare an argument to the function against a data member. This data member could be configurable so that the object could be customized for whatever comparison you want.

Of course, you could implement either of the preceding benefits with global or static variables. However, function objects provide a cleaner way to do it. The true benefits of function objects will become apparent when you learn more about the STL in Chapters 21 and 23.

By following the normal method overloading rules, you can write as many operator()s for your classes as you want. Specifically, the various operator()s must have different numbers of types of parameters. For example, you could add an operator() to the FunctionObject class that takes a string reference:

class FunctionObject

{

public:

int operator() (int inParam); void operator() (string& str); int aMethod(int inParam);

};

The function call operator can also be used to provide subscripting for multiple indices of an array. Simply write an operator() that behaves like operator[] but allows more than one parameter. The only problem with this technique is that now you have to use () to index instead of [], as in myArray(3, 4) = 6;

Overloading the Dereferencing Operators

There are three de-referencing operators you can overload: *, ->, and ->*. Ignoring ->* for the moment (we’ll get back to it later), consider the built-in meanings of * and ->. * dereferences a pointer to give you direct access to its value, while -> is shorthand for a * dereference followed by a . member selection. The following code shows the equivalences:

SpreadsheetCell* cell1 = new SpreadsheetCell; (*cell1).set(5); // Dereference plus member selection

cell1->set(5); // Shorthand arrow dereference and member selection together

449

Chapter 16

You can overload the dereferencing operators for your classes in order to make objects of the classes behave like pointers. The main use of this capability is for implementing smart pointers, which you learned about in Chapters 4, 13, and 15. It is also useful for iterators, which the STL uses and which you can think of as fancy smart pointers. Chapters 21 to 23 cover iterators in more detail, and Chapter 25 provides a sample implementation of a smart pointer class. This chapter teaches you the basic mechanics for overloading the relevant operators in the context of a simple smart pointer template class.

Here is the smart pointer template class definition, without the dereference operators filled in yet:

template <typename T> class Pointer

{

public:

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

//Dereference operators will go here. protected:

T* mPtr; private:

//Prevent assignment and pass by reference. Pointer(const Pointer<T>& src);

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

};

This smart pointer is about as simple as you can get. All it does is store a dumb pointer and delete it when the object is destroyed. The implementations are equally simple: the constructor takes a real (“dumb”) pointer, which is stored as the only data member in the class. The destructor frees the pointer.

template <typename T> Pointer<T>::Pointer(T* inPtr)

{

mPtr = inPtr;

}

template <typename T> Pointer<T>::~Pointer()

{

delete mPtr;

}

You would like to be able to use the smart pointer template like this:

#include “Pointer.h” #include “SpreadsheetCell.h” #include <iostream>

using namespace std;

int main(int argc, char** argv)

{

Pointer<int> smartInt(new int);

*smartInt = 5; // Dereference the smart pointer. cout << *smartInt << endl;

450

Overloading C++ Operators

Pointer<SpreadsheetCell> smartCell(new SpreadsheetCell);

smartCell->set(5); // Dereference and member select the set method. cout << smartCell->getValue() << endl;

return (0);

}

As you can see, you need to provide implementations of * and -> for this class.

You should rarely write operator* or operator-> alone. Always implement both together if they have appropriate semantics for your class. It would be confusing for a smart pointer–like object to support -> but not *, or vice-versa.

Implementing operator*

When you dereference a pointer, you expect to be able to access the memory to which the pointer points. If that memory contains a simple type such as an int, you should be able to change its value directly. If the memory contains a more complicated type, such as an object, you should be able to access its data members or methods with the . operator.

To provide these semantics, you should return a reference to a variable or object from operator*. In the Pointer class, the declaration and definition look like this:

template <typename T> class Pointer

{

public:

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

T& operator*();

const T& operator*() const; protected:

T* mPtr; private:

Pointer(const Pointer<T>& src);

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

};

template <typename T>

T& Pointer<T>::operator*()

{

return (*mPtr);

}

As you can see, operator* returns a reference to the object or variable to which the underlying dumb pointer points. As in overloading the subscripting operators, it’s useful to provide both const and non- const versions of the method, which return a const reference and reference, respectively. The const version is implemented identically to the non-const version, so its implementation is not shown here.

451

Chapter 16

Implementing operator->

The arrow operator is a bit trickier. The result of applying the arrow operator should be a member or method of an object. However, in order to implement it like that, you would have to be able to implement the equivalent of operator* followed by operator.. C++ doesn’t allow you to overload operator. for good reason: it’s impossible to write a single prototype that allows you to capture any possible member or method selection. Similarly, you couldn’t write an operator-> with such semantics.

Therefore, C++ treats operator-> as a special case. Consider this line:

smartCell->set(5);

C++ translates the preceding to:

(smartCell.operator->())->set(5);

As you can see, C++ applies another operator-> to whatever you return from your overloaded operator->. Therefore, you must return a pointer to an object like this:

template <typename T> class Pointer

{

public:

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

T& operator*();

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

const T* operator->() const; protected:

T* mPtr; private:

Pointer(const Pointer<T>& src);

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

};

template <typename T>

T* Pointer<T>::operator->()

{

return (mPtr);

}

Again, you should write both const and non-const forms of the operator. The implementation of the const version is identical to the non-const, so it is not shown here.

It’s unfortunate that operator* and operator-> are asymmetric, but, once you see them a few times, you’ll get used to it.

What in the World Is operator->* ?

Recall from Chapter 9 that you can manipulate pointers to members and methods of a class. When you try to dereference the pointer, it must be in the context of an object of that class. Here is the example from Chapter 9:

452