12.2. The Implicit this PointerAs 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 PointerAlthough 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:
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 *thisTo 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 FunctionIn 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.
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 constTo 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 MembersIt 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.
![]() |