Team LiB
Previous Section Next Section

14.6. Member Access Operators

14.6. 成员访问操作符

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

为了支持指针型类,例如迭代器,C++ 语言允许重载解引用操作符(*)和箭头操作符(->))。

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.

解引用操作符和箭头操作符常用在实现智能指针第 13.5.1 节)的类中。作为例子,假定想要定义一个类类型表示指向第十二章 Screen 类型对象的指针,将该类命名为 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.

ScreenPtr 类将类似于我们的第二个 HasPtr 类。ScreenPtr 的用户将会传递一个指针,该指针指向动态分配的 ScreenScreenPtr 类将拥有该指针,并安排在指向基础对象的最后一个 ScreenPtr 消失时删除基础对象。另外,不用为 ScreenPtr 类定义默认构造函数。因此,我们知道一个 ScreenPtr 对象将总是指向一个 Screen 对象,不会有未绑定的 ScreenPtr,这一点与内置指针不同。应用程序可以使用 ScreenPtr 对象而无须首先测试它是否指向一个 Screen 对象。

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:

HasPtr 类一样,ScreenPtr 类将对其指针进行使用计数。我们将定义一个伙伴类保存指针及其相关使用计数:

     // 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:

这个类看来很像 U_Ptr 类并且作用同样。ScrPtr 保存指针及其相关使用计数。将 ScreenPtr 设为友元,以便 ScreenPtr 可以访问使用计数。ScreenPtr 类将管理使用计数:

     /*
      * 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.

因为没有默认构造函数,所以 ScreenPtr 类型的每个对象必须提供一个初始化函数,初始化函数必须是另一个 ScreenPtr 对象或指向动态分配的 Screen 的指针。构造函数分配一个新的 ScrPtr 对象以保存那个那个指针及相关的使用计数。

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

试图定义一个不带初始化式的 ScreenPtr 对象是错误的:

     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.

解引用操作符是个一元操作符。在这个类中,解引用操作符定义为成员,因此没有显式形参,该操作符返回对 ScreenPtr 所指向的 Screen 的引用。

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.

像下标操作符一样,我们需要解引用操作符的 const 和非 const 版本。它们的区别在于返回类型:const 成员返回 const 引用以防止用户改变基础对象。

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:

换句话说,我们想要调用的是对 point->action 求值的结果。编译器这样对该代码进行求值:

  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.

    如果 point 是一个指针,指向具有名为 action 的成员的类对象,则编译器将代码编译为调用该对象的 action 成员。

  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.

    否则,如果 action 是定义了 operator-> 操作符的类的一个对象,则 point->actionpoint.operator->()->action 相同。即,执行 pointoperator->(),然后使用该结果重复这三步。

  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 对象访问 Screen 对象的成员:

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.

因为 p 是一个 ScreenPtr 对象,p->display 的含义与对 (p.operator->())->display 求值相同。对 p.operator->() 求值将调用 ScreenPtr 类的 operator->,它返回指向 Screen 对象的指针,该指针用于获取并运行 ScreenPtr 所指对象的 display 成员。

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.

ScreenPtr 类的概略定义中,声明但没有定义赋值操作符。请实现 ScreenPtr 赋值操作符。

Exercise 14.21:

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

定义一个类,该类保存一个指向 ScreenPtr 的指针。为该类定义一个重载的箭头操作符。

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.

智能指针可能应该定义相等操作符和不等操作符,以便测试两个指针是否相等或不等。将这些操作加入到 ScreenPtr 类。


Team LiB
Previous Section Next Section