9.10. The this Pointer: class stack

Time: 00:03:01 | Download: Large, Large (CC), Small | Streaming, Streaming (CC) | Slides: PDF, PPTX
Review

Aside from making a grammatically awkward sentence, what is the "this" pointer, and why is it important? To help answer these questions, we expand on our initial introduction to the this pointer and the accompanying example. We'll explore how C++ programs initialize the this pointer to bind an object to a member function. To demonstrate one way that objects simplify programming, the section converts the Chapter 7 structure-based stack and its associated functions to a class with member functions.

const int SIZE = 100;

struct stack
{
	char	st[SIZE];
	int	sp;
};

stack	make_stack();
void	init_stack(stack* s);
void	push(stack* s, char data);
char	pop(stack* s);
int	size(stack* s);
char	peek(stack* s);
 
void push(stack* s, char data)
{
    if (s->sp < SIZE)
	s->st[s->sp++] = data;
    else
	cerr << "ERROR: stack is full" << endl;
}

char pop(stack* s)
{
    if (s->sp > 0)
	return s->st[s->--sp];
    else
	cerr << "ERROR: stack is empty" << endl;
}
(a)(b)
A stack implemented as a structure. A stack is an abstract data type (ADT) that programs can implement in many ways. A non-object-oriented program can implement a stack as two discrete variables: char stack[100]; and int sp;. Although effective, this solution is inadequate if the program needs multiple stacks. The program can still solve this problem without object-oriented programming by creating a structure (or its equivalent in the implementation language) to group the array and the stack pointer. In the illustrated C version, most of the stack functions require a parameter, s, to "tell" the function on which stack to operate.
An unfortunate collision of terms warrants some caution: Array-based stack implementations often include a variable called a stack pointer, which we must not confuse with a variable (e.g., stack* s) that points to a specific stack.
  1. During a function call, parameter s binds the function to the stack on which it operates, allowing programs to define and use multiple stacks without confusion or conflict.
  2. Two functions illustrate the syntax member function use with the pointer-to-a-stack parameter, s, to access the object's data members. The stack pointer, sp, points to an empty array element at the top of the stack. The push operation stores data in this empty location, but pop returns the data one space below it. To make sp work for both operations, push necessarily uses the post-increment operator, sp++, and pop uses the pre-decrement operator, --sp. Seen as a complete expression, the sequence of operators can be intimidating:
    • s->: The arrow operator selects members, either sp or st, through an object pointer
    • st[...]: The ellipsis represents omitted detail, so the index operator, [], selects an element from array st
    • s->sp: Ignoring the increment and decrement operators for a moment, the arrow operator selects sp
    • s->st[s->sp++] = data: the index operator locates element sp in the array, the assignment operator stores a value in that element, and the increment operator increases the stack pointer by one
    • return s->st[s->--sp]: the decrement operator decreases the stack pointer by one, the index operator selects the indexed element in the array, and the return returns the stored value

The following figures convert the structure-based stack to a class with member functions. The conversion merges make_stack and init_stack into a single default constructor. Three functions, the constructor, push, and pop modify the stack, requiring an INOUT passing mechanism - a pointer or a reference. Follow the parameter, stack* s and its corresponding argument throughout the conversion process.

The Role of the this Pointer

A natural question to ask is, "If we always pass a stack pointer to every stack function, can the compiler automate the process to make it easier?" Fortunately, it can! The following figure begins by converting the stack structure to a class:

class stack
{
	private:
		static const int SIZE = 100;

		char	st[SIZE];
		int	sp;

	public:
			stack() : sp(0) {}
		void	push(char data);
		char	pop();
		int	size();
		char	peek();
};
A stack implemented as a class. The definition of SIZE is moved into the stack class and made a static member. make_stack and init_stack are replaced with a single default constructor. The object-oriented version makes all the stackfunctions members of the class. Significantly, the member functions no longer need the explicit parameter, stack* s, and the class removes them.

Converting the stack from a structure to a class changes the appearance of the functions but not their behavior. When called, the functions must still "know" on which stack they are operating, which means each function must be bound to an object when it runs. Furthermore, this binding occurs in the same way as in the structure version: the program passes the stack's address to the function as an argument. The programmer must explicitly calculate the stack's address in the structure version and pass it as an explicit argument. The C++ compiler does both steps automatically for member functions.

Whenever the program calls a member function, the calling object's address is automatically passed as an "invisible" or implicit pointer argument. Invisible or not, the program saves every value it passes to a function in a local parameter variable. In the struct version of the stack, that argument and how the function uses it are visible in the function definitions, as illustrated in Figure 1. In the class version of the stack, the compiler automatically passes the address of the bound object and generates a "receiving" variable or parameter named this. The following figure contrasts the code that a programmer writes with the code that the compiler automatically generates.

Programmer Writes Compiler Generates
s.push('a');
s.push(&s, 'a');
void stack::push(char data)
{
    if (sp < SIZE)
        st[sp++] = data;
    else
        throw "Stack Overflow";
}
void stack::push(stack* this, char data)
{
    if (this->sp < SIZE)
        this->st[this->sp++] = data;
    else
        throw "Stack Overflow";
}
The C++ compiler automatically generates the function-object binding. The illustration assumes that a program creates a stack object, s, and calls its push function. The function-to-object binding does not appear in the source code that the programmer writes. However, the compiler injects machine code effectively implementing the object-function binding displayed in red. Some programmers include this-> in the source code, but it is optional: this->st[this->sp++] = data; and st[sp++] = data; are equivalent and produce the same results.
this

The this pointer is an automatic local variable that the compiler generates for every non-static member function, saving the address of the object that calls the function. The object-to-function binding through the this pointer begins when the program calls any non-static member function and lasts until the function ends and returns. Let o represent an arbitrary object with the member function f; while f is running, within its body or scope, this == &o.

#include<iostream>
#include "stack.h"
using namespace std;

void stack::push(char data)
{
	if (sp < SIZE)
		st[sp++] = data;
	else
		throw "Stack Overflow";
}

char stack::pop()
{
	if (sp > 0)
		return st[--sp];
	else
		throw "Stack Underflow";
}

int stack::size()
{
	return sp;
}

char stack::peek()
{
	return st[sp - 1];
}
The stack class member functions. The final version of the stack member functions. The push and pop operations test for overflow (pushing data on a full stack) and underflow (popping data off an empty stack) conditions, respectively, throwing an exception whenever they detect an error condition.

Visualizing The this Pointer

The compiler automatically defines a transparent pointer parameter named this in every non-static member function. Furthermore, the compiler adds the address of the calling object as an implicit argument to every member function call. Therefore, the program establishes the binding between an object and a member function when it calls the function and dissolves it when it ends, making the binding between an object and a member function temporary.

stack	r;
stack	s;
stack	t;
Three rectangles represent three stack objects named r, s, and t. Each object has two member variables named sp and st.
(a)
s.push('a');
A picture illustrating that when object s calls the print function, it is bound to the function by the 'this' pointer. The function call passes the address of s to the 'this' pointer, which points to object s until the function call returns.
(b)
t.push('a');
Similar to the previous picture, but now illustrating that when object r calls the print function, it is bound to the function by the 'this' pointer. The function call passes the address of r to the 'this' pointer, which points to object r until the function call returns.
(c)
this binds objects to member functions. Although not visible in a member function definition, the compiler automatically adds this as a parameter. Like all automatic local variables, the program allocates and deallocates its memory for it when calling and returning from the function. In this example, the program can pass the address of any stack object to the push function, binding the object and the function with the this parameter.
  1. The statements instantiate three distinct stack objects
  2. this stores the address of s while the push operation is running
  3. this stores the address of t while the push operation is running

Downloadable stack Class Example Code

ViewDownloadComments
stack.h access.h
stack.cpp stack.cpp