Get up to 80 % extra points for free! More info:

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++

 

Previous article
Introducing destructors and more about constructors in C++
All articles in this section
Object-Oriented Programming in C++
Skip article
(not recommended)
Arena Warrior - Encapsulation
Article has been written for you by David Capka Hartinger
Avatar
User rating:
No one has rated this quite yet, be the first one!
The author is a programmer, who likes web technologies and being the lead/chief article writer at ICT.social. He shares his knowledge with the community and is always looking to improve. He believes that anyone can do what they set their mind to.
Unicorn university David learned IT at the Unicorn University - a prestigious college providing education on IT and economics.
Activities