Section 14.6. Member Access Operators


14.6. Member Access Operators

To support pointerlike classes, such as iterators, the language allows the dereference (*) and arrow (->) operators to be overloaded.

Operator arrow must be defined as a class member function. The dereference operator is not required to be a member, but it is usually right to make it a member as well.



Building a Safer Pointer

The dereference and arrow operators are often used in classes that implement smart pointers (Section 13.5.1, p. 495). As an example, let's assume that we want to define a class type to represent a pointer to an object of the Screen type that we wrote in Chapter 12. We'll name this class ScreenPtr.

Our ScreenPtr class will be similar to our second HasPtr class. Users of ScreenPtr will be expected to pass a pointer to a dynamically allocated Screen. The ScreenPtr class will own that pointer and arrange to delete the underlying object when the last ScreenPtr referring to it goes away. In addition, we will not give our ScreenPtr class a default constructor. This way we'll know that a ScreenPtr object will always refer to a Screen. Unlike a built-in pointer, there will be no unbound ScreenPtrs. Applications can use ScreenPtr objects without first testing whether they refer to a Screen object.

As does the HasPtr class, the ScreenPtr class will use-count its pointer. We'll define a companion class to hold the pointer and its associated use count:

      // private class for use by ScreenPtr only      class ScrPtr {          friend class ScreenPtr;          Screen *sp;          size_t use;          ScrPtr(Screen *p): sp(p), use(1) { }          ~ScrPtr() { delete sp; }      }; 

This class looks a lot like the U_Ptr class and has the same role. ScrPtr holds the pointer and associated use count. We make ScreenPtr a friend so that it can access the use count. The ScreenPtr class manages the use count:

      /*       * smart pointer: Users pass to a pointer to a dynamically allocated Screen, which       *                   is automatically destroyed when the last ScreenPtr goes away       */      class ScreenPtr {      public:          //  no default constructor: ScreenPtrs must be bound to an object          ScreenPtr(Screen *p): ptr(new ScrPtr(p)) { }          //  copy members and increment the use count          ScreenPtr(const ScreenPtr &orig):             ptr(orig.ptr) { ++ptr->use; }          ScreenPtr& operator=(const ScreenPtr&);          //  if use count goes to zero, delete the ScrPtr object          ~ScreenPtr() { if (--ptr->use == 0) delete ptr; }      private:          ScrPtr *ptr;    // points to use-counted ScrPtr class      }; 

Because there is no default constructor, every object of type ScreenPtr must provide an initializer. The initializer must be another ScreenPtr or a pointer to a dynamically allocated Screen. The constructor allocates a new ScrPtr object to hold that pointer and an associated use count.

An attempt to define a ScreenPtr with no initializer is in error:

      ScreenPtr p1; // error: ScreenPtr has no default constructor      ScreenPtr ps(new Screen(4,4));     // ok: ps points to a copy of myScreen 

Supporting Pointer Operations

Among the fundamental operations a pointer supports are dereference and arrow. We can give our class these operations as follows:

      class ScreenPtr {      public:          // constructor and copy control members as before          Screen &operator*() { return *ptr->sp; }          Screen *operator->() { return ptr->sp; }          const Screen &operator*() const { return *ptr->sp; }          const Screen *operator->() const { return ptr->sp; }      private:          ScrPtr *ptr; // points to use-counted ScrPtr class      }; 

Overloading the Dereference Operator

The dereference operator is a unary operator. In this class, it is defined as a member so it has no explicit parameters. The operator returns a reference to the Screen to which this ScreenPtr points.

As with the subscript operator, we need both const and nonconst versions of the dereference operator. These differ in their return types: The const member returns a reference to const to prevent users from changing the underlying object.

Overloading the Arrow Operator

Operator arrow is unusual. It may appear to be a binary operator that takes an object and a member name, dereferencing the object in order to fetch the member. Despite appearances, the arrow operator takes no explicit parameter.

There is no second parameter because the right-hand operand of -> is not an expression. Rather, the right-hand operand is an identifier that corresponds to a member of a class. There is no obvious, useful way to pass an identifier as a parameter to a function. Instead, the compiler handles the work of fetching the member.

When we write

      point->action(); 

precedence rules make it equivalent to writing

      (point->action)(); 

In other words, we want to call the result of evaluating point->action. The compiler evaluates this code as follows:

  1. If point is a pointer to a class object that has a member named action, then the compiler writes code to call the action member of that object.

  2. Otherwise, if point is an object of a class that defines operator->, then point->action is the same as point.operator->()->action. That is, we execute operator->() on point and then repeat these three steps, using the result of executing operator-> on point.

  3. Otherwise, the code is in error.

Using Overloaded Arrow

We can use a ScreenPtr object to access members of a Screen as follows:

 ScreenPtr p(&myScreen);     // copies the underlying Screen p->display(cout); 

Because p is a ScreenPtr, the meaning of p->display isthe same as evaluating (p.operator->())->display. Evaluating p.operator->() calls the operator-> from class ScreenPtr, which returns a pointer to a Screen object. That pointer is used to fetch and run the display member of the object to which the ScreenPtr points.

Constraints on the Return from Overloaded Arrow

The overloaded arrow operator must return either a pointer to a class type or an object of a class type that defines its own operator arrow.



If the return type is a pointer, then the built-in arrow operator is applied to that pointer. The compiler dereferences the pointer and fetches the indicated member from the resulting object. If the type pointed to does not define that member, then the compiler generates an error.

If the return value is another object of class type (or reference to such an object), then the operator is applied recursively. The compiler checks whether the type of the object returned has a member arrow and if so, applies that operator. Otherwise, the compiler generates an error. This process continues until either a pointer to an object with the indicated member is returned or some other value is returned, in which case the code is in error.

Exercises Section 14.6

Exercise 14.20:

In our sketch for the ScreenPtr class, we declared but did not define the assignment operator. Implement the ScreenPtr assignment operator.

Exercise 14.21:

Define a class that holds a pointer to a ScreenPtr. Define the overloaded arrow operator for that class.

Exercise 14.22:

A smart pointer probably should define the equality and inequality operators to test whether two pointers are equal or unequal. Add these operations to the ScreenPtr class.




C++ Primer
C Primer Plus (5th Edition)
ISBN: 0672326965
EAN: 2147483647
Year: 2006
Pages: 223
Authors: Stephen Prata

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net