Team LiB
Previous Section Next Section

12.2. The Implicit this Pointer

12.2. 隐含的 this 指针

As we saw in Section 7.7.1 (p. 260), member functions have an extra implicit parameter that is a pointer to an object of the class type. This implicit parameter is named this, and is bound to the object on which the member function is called. Member functions may not define the this parameter; the compiler does so implicitly. The body of a member function may explicitly use the this pointer, but is not required to do so. The compiler treats an unqualified reference to a class member as if it had been made through the this pointer.

第 7.7.1 节中已经提到,成员函数具有一个附加的隐含形参,即指向该类对象的一个指针。这个隐含形参命名为 this,与调用成员函数的对象绑定在一起。成员函数不能定义 this 形参,而是由编译器隐含地定义。成员函数的函数体可以显式使用 this 指针,但不是必须这么做。如果对类成员的引用没有限定,编译器会将这种引用处理成通过 this 指针的引用。

When to Use the this Pointer

何时使用 this 指针

Although it is usually unnecessary to refer explicitly to this inside a member function, there is one case in which we must do so: when we need to refer to the object as a whole rather than to a member of the object. The most common case where we must use this is in functions that return a reference to the object on which they were invoked.

尽管在成员函数内部显式引用 this 通常是不必要的,但有一种情况下必须这样做:当我们需要将一个对象作为整体引用而不是引用对象的一个成员时。最常见的情况是在这样的函数中使用 this:该函数返回对调用该函数的对象的引用。

The Screen class is a good example of the kind of class that might have operations that should return references. So far our class has only a pair of get operations. We might logically add:

某种类可能具有某些操作,这些操作应该返回引用,Screen 类就是这样的一个类。迄今为止,我们的类只有一对 get 操作。逻辑上,我们可以添加下面的操作。

  • A pair of set operations to set either a specified character or the character denoted by the cursor to a given value

    一对 set 操作,将特定字符或光标指向的字符设置为给定值。

  • A move operation that, given two index values, moves the cursor to that new position

    一个 move 操作,给定两个 index 值,将光标移至新位置。

Ideally, we'd like users to be able to concatenate a sequence of these actions into a single expression:

理想情况下,希望用户能够将这些操作的序列连接成一个单独的表达式:

     // move cursor to given position, and set that character
     myScreen.move(4,0).set('#');

We'd like this statement to be equivalent to

这个语句等价于:

     myScreen.move(4,0);
     myScreen.set('#');

Returning *this

返回 *this

To allow us to call move and set in a single expression, each of our new operations must return a reference to the object on which it executes:

在单个表达式中调用 moveset 操作时,每个操作必须返回一个引用,该引用指向执行操作的那个对象:

     class Screen {
     public:
          // interface member functions
          Screen& move(index r, index c);
          Screen& set(char);
          Screen& set(index, index, char);
          // other members as before
     };

Notice that the return type of these functions is Screen&, which indicates that the member function returns a reference to an object of its own class type. Each of these functions returns the object on which it was invoked. We'll use the this pointer to get access to the object. Here is the implementation for two of our new members:

注意,这些函数的返回类型是 Screen&,指明该成员函数返回对其自身类类型的对象的引用。每个函数都返回调用自己的那个对象。使用 this 指针来访问该对象。下面是对两个新成员的实现:

     Screen& Screen::set(char c)
     {
         contents[cursor] = c;
         return *this;
     }
     Screen& Screen::move(index r, index c)
     {
         index row = r * width; // row location
         cursor = row + c;
         return *this;
     }

The only interesting part in this function is the return statement. In each case, the function returns *this. In these functions, this is a pointer to a nonconst Screen. As with any pointer, we can access the object to which this points by dereferencing the this pointer.

函数中唯一需要关注的部分是 return 语句。在这两个操作中,每个函数都返回 *this。在这些函数中,this 是一个指向非常量 Screen 的指针。如同任意的指针一样,可以通过对 this 指针解引用来访问 this 指向的对象。

Returning *this from a const Member Function

const 成员函数返回 *this

In an ordinary nonconst member function, the type of this is a const pointer (Section 4.2.5, p. 126) to the class type. We may change the value to which this points but cannot change the address that this holds. In a const member function, the type of this is a const pointer to a const class-type object. We may change neither the object to which this points nor the address that this holds.

在普通的非 const 成员函数中,this 的类型是一个指向类类型的 const 指针(第 4.2.5 节)。可以改变 this 所指向的值,但不能改变 this 所保存的地址。在 const 成员函数中,this 的类型是一个指向 const 类类型对象的 const 指针。既不能改变 this 所指向的对象,也不能改变 this 所保存的地址。

We cannot return a plain reference to the class object from a const member function. A const member function may return *this only as a const reference.

不能从 const 成员函数返回指向类对象的普通引用。const 成员函数只能返回 *this 作为一个 const 引用。



As an example, we might add a display operation to our Screen class. This function should print contents on a given ostream. Logically, this operation should be a const member. Printing the contents doesn't change the object. If we make display a const member of Screen, then the this pointer inside display will be a const Screen* const.

例如,我们可以给 Screen 类增加一个 display 操作。这个函数应该在给定的 ostream 上打印 contents。逻辑上,这个操作应该是一个 const 成员。打印 contents 不会改变对象。如果将 display 作为 Screenconst 成员,则 display 内部的 this 指针将是一个 const Screen* 型的 const

However, as we can with the move and set operations, we'd like to be able to use the display in a series of actions:

然而,与 moveset 操作一样,我们希望能够在一个操作序列中使用 display

     // move cursor to given position, set that character and display the screen
     myScreen.move(4,0).set('#').display(cout);

This usage implies that display should return a Screen reference and take a reference to an ostream. If display is a const member, then its return type must be const Screen&.

这个用法暗示了 display 应该返回一个 Screen 引用,并接受一个 ostream 引用。如果 display 是一个 const 成员,则它的返回类型必须是 const Screen&

Unfortunately, there is a problem with this design. If we define display as a const member, then we could call display on a nonconst object but would not be able to embed a call to display in a larger expression. The following code would be illegal:

不幸的是,这个设计存在一个问题。如果将 display 定义为 const 成员,就可以在非 const 对象上调用 display,但不能将对 display 的调用嵌入到一个长表达式中。下面的代码将是非法的:

     Screen myScreen;
     // this code fails if display is a const member function
     // display return a const reference; we cannot call set on a const
     myScreen.display().set('*');

The problem is that this expression runs set on the object returned from display. That object is const because display returns its object as a const. We cannot call set on a const object.

问题在于这个表达式是在由 display 返回的对象上运行 set。该对象是 const,因为 display 将其对象作为 const 返回。我们不能在 const 对象上调用 set

Overloading Based on const

基于 const 的重载

To solve this problem we must define two display operations: one that is const and one that isn't. We can overload a member function based on whether it is const for the same reasons that we can overload a function based on whether a pointer parameter points to const (Section 7.8.4, p. 275). A const object will use only the const member. A nonconst object could use either member, but the nonconst version is a better match.

为了解决这个问题,我们必须定义两个 display 操作:一个是 const,另一个不是 const。基于成员函数是否为 const,可以重载一个成员函数;同样地,基于一个指针形参是否指向 const第 7.8.4 节),可以重载一个函数。const 对象只能使用 const 成员。非 const 对象可以使用任一成员,但非 const 版本是一个更好的匹配。

While we're at it, we'll define a private member named do_display to do the actual work of printing the Screen. Each of the display operations will call this function and then return the object on which it is executing:

在此,我们将定义一个名为 do_displayprivate 成员来打印 Screen。每个 display 操作都将调用此函数,然后返回调用自己的那个对象:

     class Screen {
     public:
         // interface member functions
         // display overloaded on whether the object is const or not
         Screen& display(std::ostream &os)
                       { do_display(os); return *this; }
         const Screen& display(std::ostream &os) const
                       { do_display(os); return *this; }
     private:
          // single function to do the work of displaying a Screen,
          // will be called by the display operations
          void do_display(std::ostream &os) const
                            { os << contents; }
          // as before
      };

Now, when we embed display in a larger expression, the nonconst version will be called. When we display a const object, then the const version is called:

现在,当我们将 display 嵌入到一个长表达式中时,将调用非 const 版本。当我们 display 一个 const 对象时,就调用 const 版本:

     Screen myScreen(5,3);
     const Screen blank(5, 3);
     myScreen.set('#').display(cout); // calls nonconst version
     blank.display(cout);             // calls const version

Mutable Data Members

可变数据成员

It sometimes (but not very often) happens that a class has a data member that we want to be able to modify, even inside a const member function. We can indicate such members by declaring them as mutable.

有时(但不是很经常),我们希望类的数据成员(甚至在 const 成员函数内)可以修改。这可以通过将它们声明为 mutable 来实现。

A mutable data member is a member that is never const, even when it is a member of a const object. Accordingly, a const member function may change a mutable member. To declare a data member as mutable, the keyword mutable must precede the declaration of the member:

可变数据成员(mutable data member)永远都不能为 const,甚至当它是 const 对象的成员时也如此。因此,const 成员函数可以改变 mutable 成员。要将数据成员声明为可变的,必须将关键字 mutable 放在成员声明之前:

     class Screen {
     public:
     // interface member functions
     private:
         mutable size_t access_ctr; // may change in a const members
         // other data members as before
      };

We've given Screen a new data member named access_ctr that is mutable. We'll use access_ctr to track how often Screen member functions are called:

我们给 Screen 添加了一个新的可变数据成员 access_ctr。使用 access_ctr 来跟踪调用 Screen 成员函数的频繁程度:

     void Screen::do_display(std::ostream& os) const
     {
         ++access_ctr; // keep count of calls to any member function
         os << contents;
     }

Even though do_display is const, it can increment access_ctr. That member is a mutable member, so any member function, including const functions, can change the value of access_ctr.

尽管 do_displayconst,它也可以增加 access_ctr。该成员是可变成员,所以,任意成员函数,包括 const 函数,都可以改变 access_ctr 的值。

Advice: Use Private Utility Functions for Common Code

建议:用于公共代码的私有实用函数

Some readers might be surprised that we bothered to define a separate do_display operation. After all, the calls to do_display aren't much simpler than the action done inside do_display. Why bother? We do so for several reasons:

有些读者可能会奇怪为什么要费力地单独定义一个 do_display 内部所做的操作更简单。为什么还要如此麻烦?我们这样做有下面几个原因。

  1. A general desire to avoid writing the same code in more than one place.

    一般愿望是避免在多个地方编写同样的代码。

  2. The display operation can be expected to become more complicated as our class evolves. As the actions involved become more complex, it makes more obvious sense to write those actions in one place, not two.

    display 操作预期会随着类的演变而变得更复杂。当所涉及的动作变得更复杂时,只在一处而不是两处编写这些动作有更显著的意义。

  3. It is likely that we might want to add debugging information to do_display during development that would be eliminated in the final product version of the code. It will be easier to do so if only one definition of do_display needs to be changed to add or remove the debugging code.

    很可能我们会希望在开发时给 do_display 增加调试信息,这些调试信息将会在代码的最终成品版本中去掉。如果只需要改变一个 do_display 的定义来增加或删除调试代码,这样做将更容易。

  4. There needn't be any overhead involved in this extra function call. We made do_display inline, so the run-time performance between calling do_display or putting the code directly into the display operations should be identical.

    这个额外的函数调用不需要涉及任何开销。我们使 do_display 成为内联的,所以调用 do_display 与将代码直接放入 display 操作的运行时性能应该是相同的。

In practice, well-designed C++ programs tend to have lots of small functions such as do_display that are called to do the "real" work of some other set of functions.

实际上,设计良好的 C++ 程序经常具有许多像 do_display 这样的小函数,它们被调用来完成一些其他函数的“实际”工作。


Exercises Section 12.2

Exercise 12.13:

Extend your version of the Screen class to include the move, set, and display operations. Test your class by executing the expression:

扩展 Screen 类以包含 movesetdisplay 操作。通过执行如下表达式来测试类:



     // move cursor to given position, set that character and
 display the screen
     myScreen.move(4,0).set('#').display(cout);

Exercise 12.14:

It is legal but redundant to refer to members through the this pointer. Discuss the pros and cons of explicitly using the this pointer to access members.

通过 this 指针引用成员虽然合法,但却是多余的。讨论显式使用 this 指针访问成员的优缺点。


Team LiB
Previous Section Next Section