Lesson 5 - The this pointer in C++
In the previous lesson, Introducing destructors and more about constructors in C++, we finished constructors and promised to
get rid of the ugly parameter names starting with an underscore _
today. We had to prefix the parameters with underscores because C++ wouldn't
know which variable the command refers to (whether to a field or a parameter).
The this
pointer will help us with this problem.
Pointer
this
is a C++ keyword and we can't create a variable, class, or
any other type named this
. As mentioned, this
is a
pointer accessible in all class methods and refers to the instance
itself. This language construct often causes confusion, so let's start
it easy. this
is a pointer to the instance itself, so it must be of
the same type as the class is. We can demonstrate it, for example, on the
Player
class, where we change the constructor as follows:
Player.cpp
#include "Player.h" Player::Player(string _name) { Player const * current = this; name = _name; }
If we try to assign the pointer to any other type (for example,
int
), then the compiler will report the following error (for Visual
Studio):
error C2440: 'initializing': cannot convert from 'Player *const ' to 'int *' Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast
This means that we can't convert a pointer of the Player * const
type to the int *
type. At the same time, the compiler tells us the
type of the this
pointer. This is a constant pointer (see the
lesson on Constant values). We can change the instance it points to, but we
can't change the pointer's value (const
after the asterisk).
Therefore, the following code will not be valid:
this = new Player("Carl");
The compilation reports:
error C2106: '=': left operand must be l-value
So now we know what this
actually is. Let's also figure out what
it refers to.
A roll() method example
As we've already said, this
points to the instance itself. For
example, we'll edit the roll()
method of the
RollingDie
class to accept a pointer to a RollingDie
type as a parameter:
RollingDie.h
class RollingDie { public: RollingDie(); RollingDie(int _sides_count); ~RollingDie(); int sides_count; int roll(RollingDie* die); };
RollingDie.cpp
// ...previous implementation int RollingDie::roll(RollingDie* die) { return rand() % sides_count + 1; }
Now when calling the roll()
method in main.cpp
,
we'll pass it a pointer to the instance itself:
// main.cpp RollingDie die; for (int i = 0; i < 10; i++) die.roll(&die); cout << endl;
And how do we prove that this
really refers to this instance?
We'll compare the pointers' addresses - we'll modify the roll()
method, and then run the program.
int RollingDie::roll(RollingDie* die) { cout << "Address of this: " << this << endl; cout << "Address of the parameter: " << die << endl; return rand() % sides_count + 1; }
Note: The addresses may be different, but the pair should be the same.
Console application
Address of this: 0x7ffc781864e0
Address of the parameter: 0x7ffc781864e0
If we create two dice, the addresses will be different:
int main()
{
RollingDie first;
RollingDie second;
first.roll(&first);
second.roll(&second);
}
Console application
Address of this: 0x7ffe01f06b40
Address of the parameter: 0x7ffe01f06b40
Address of this: 0x7ffe01f06b30
Address of the parameter: 0x7ffe01f06b30
Simplifying parameter names with this
What does that all mean? this
can be considered as a
pointer that refers to the instance on which we've called the method.
This pointer is available in all methods (including constructors and
destructors) and we'll use this fact now. We'll undo all code changes we've made
so far today (or just download the project from the previous lesson).
Now we can get rid of those nasty parameter names. What was the problem? If we used a parameter with the same name as the field, this parameter made the field inaccessible and we only worked with the parameter. For example, for the die, if we change the constructor to:
RollingDie.h
class RollingDie { public: RollingDie(); RollingDie(int sides_count); ~RollingDie(); int sides_count; int roll(); };
RolligDie.cpp
//...remaining implementation RollingDie::RollingDie(int sides_count) { cout << "Parametric constructor called" << endl; sides_count = sides_count; // we pass the value from the parameter to the variable we passed as a parameter srand(time(NULL)); }
We have to say somehow that we want to use a variable of the instance.
But the instance itself is in the this
pointer!
RolligDie.cpp
//...remaining implementaton RollingDie::RollingDie(int sides_count) { cout << "Parametric constructor called" << endl; this->sides_count = sides_count; // we store the value from the parameter to the instance variable srand(time(NULL)); }
In the same way, we'll also modify the Arena
and
Player
classes. By this, we're actually done with the practical
part in this lesson.
To use or not to use this
We didn't know about the this
pointer until today's lesson and
we were able to change the class fields anyway. If there's no variable (it
doesn't necessarily have to be a parameter) that has the same name as the field,
we don't have to (but we can) use this
. Some languages (like Java or C#) work the same as C++ and don't
require the use of this
unless it's necessary. On the other hand,
languages such as PHP or Python always
require the this
pointer to be used to access class fields. For
example, in C++, the arena's destructor can be written in two ways, and both
will work:
Arena::~Arena() { for (int i = 0; i < players_count; i++) delete players[i]; delete[] players; players = NULL; } Arena::~Arena() { for (int i = 0; i < this->players_count; i++) delete players[i]; delete[] players; players = NULL; }
There's no rule which variant we should use and it's up to every programmer to choose. Personally, I prefer the second option (although it's longer) because it makes using the class field more explicit. Therefore, I'll use this notation further in the course (but it's not necessary).
Just as we can access class fields, we can also call instance methods. For
example, if we want to call the roll()
method from the constructor
(for any reason), we can do this by the method name only, or by using
this
. Both approaches follow:
RollingDie::RollingDie(int sides_count) { cout << "Parametric constructor called" << endl; this->sides_count = sides_count; // we need to use this here since there's a parameter of the same name srand(time(NULL)); roll(); // we don't have to use this here since there's no other roll() } RollingDie::RollingDie(int sides_count) { cout << "Parametric constructor called" << endl; this->sides_count = sides_count; srand(time(NULL)); this->roll(); }
This makes today's lesson complete. In the next lesson, Arena Warrior - Encapsulation, we'll create warriors for our arena.
Did you have a problem with anything? Download the sample application below and compare it with your project, you will find the error easily.
Download
By downloading the following file, you agree to the license terms
Downloaded 2x (1 MB)
Application includes source codes in language C++