Section 12.2. The Implicit this Pointer


12.2. The Implicit this Pointer

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.

When to Use the this Pointer

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.

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:

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

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

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

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:

      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& 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.

Returning *this from a const Member Function

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.

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.



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.

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:

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

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:

      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.

Overloading Based on 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.

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:

      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:

      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.

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:

      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:

      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.

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:

  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.

  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.

  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.

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.


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:

[View full width]

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




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