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

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:
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:
// 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; }
};
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:
/*
* 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
};
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
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:
(point->action)();
- 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.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.Otherwise, the code is in error.
Using Overloaded Arrow
We can use a ScreenPtr object to access members of a Screen as follows:
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.
ScreenPtr p(&myScreen); // copies the underlying Screen
p->display(cout);
Constraints on the Return from Overloaded Arrow

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.