The this Pointer

Learn about the this pointer in C++ programming, focusing on its application in identifying callers, chaining functions, and overloading operators.

Ryan McCombe
Updated

Within our member functions, we have access to a specific keyword: this.

The this keyword stores a pointer to the object that our member function was called on.

For example, if we were to call SomeObject.SomeFunction(), within the body of SomeFunction(), the this keyword would contain a pointer to SomeObject.

Below, we show this in action. We're logging out the address of SomeObject using the address-of operator, &.

Then we're calling SomeFunction() on that same object, which is logging out the value of this:

#include <iostream>
using namespace std;

struct SomeType {
  void SomeFunction(){
    cout << this;
  }
};

int main(){
  SomeType SomeObject;
  cout << &SomeObject << '\n';
  SomeObject.SomeFunction();
}

We can see from the output that both of the memory addresses are the same:

000000C05694FBF4
000000C05694FBF4

Dereferencing this

We can use this like any pointer. We can store it in a variable or use it as an argument to a different function call.

We can also dereference it using the * operator to access the object itself. In the following example:

  • the Pointer function returns a pointer to the current object, using this and a return type of SomeType*
  • the Reference function returns a reference to the current object, using *this and a return type of SomeType&
  • the Copy function returns a copy of the current object, using *this and a return type of SomeType
struct SomeType {
  SomeType* Pointer(){ return this; }
  SomeType& Reference(){ return *this; }
  SomeType  Copy(){ return *this; }
};

There are many scenarios where the this pointer is useful. The rest of this lesson will focus on some applications.

Use Case 1: Identifying the Caller

Sometimes, our functions need to know who called them.

For example, let's imagine we have a Character class, where objects can Attack() each other.

When a character is attacked, they will likely want to know who attacked it, so it can react accordingly.

The this pointer can help us achieve that:

#include <iostream>
using namespace std;

class Character {
public:
  Character(string Name) : mName{Name}{}

  void Attack(Character& Target){
    Target.TakeDamage(*this); 
  }

  void TakeDamage(Character& Attacker){
    cout << Attacker.mName
      << " has attacked me!";
  }

  string mName;
};

int main(){
  Character Player{"Player One"};
  Character Monster{"Goblin"};

  Player.Attack(Monster);
}
Player One has attacked me!

Use Case 2: Chaining Function Calls

Another scenario where we might want to use this is to return the object that the method was called on.

This allows us to create APIs where member functions can be chained together. We show an example of this below, where we have two setters, each returning a Character&, that is, a reference to a Character.

We generate these by dereferencing the this pointer using the * operator:

#include <iostream>
using namespace std;

class Character {
public:
  Character& SetName(string Name){
    mName = Name;
    return *this;
  }

  Character& SetLevel(int Level){
    mLevel = Level;
    return *this;
  }

  Character& Log(){
    cout << "I am a " << mName
      << " and I am level " << mLevel;
    return *this;
  }

private:
  string mName;
  int mLevel;
};

int main(){
  Character Enemy;

  Enemy
    .SetName("Goblin Warrior")
    .SetLevel(10)
    .Log();
}
I am a Goblin Warrior and I am level 10

Use Case 3: Overloading Operators

A final use case for the this pointer that is worth highlighting is related to chaining functions. Operators are functions too, and we sometimes want to chain them.

For example, operators such as ++ and *= are generally expected to be chainable. If our custom type supports these operators, an expression like this should be possible:

(++x) *= 2;

The expectation here would be that x be incremented, and then its value doubled. For this to work correctly, the prefix ++ operator would need to return a reference to its operand - x in this case.

Therefore, if we wanted to implement these operators correctly for our custom type, we need to use the this keyword.

The following example is overloading the prefix form of the ++ operator, for use in expressions like ++SomeVector. We cover the postfix variation (to enable SomeVector++) later in the lesson.

#include <iostream>
using namespace std;

struct Vector3 {
  float x;
  float y;
  float z;

  Vector3& operator++(){
    ++x;
    ++y;
    ++z;
    return *this;
  }

  Vector3& operator*=(int Multiplier){
    x *= Multiplier;
    y *= Multiplier;
    z *= Multiplier;
    return *this;
  }
};

int main(){
  Vector3 SomeVector{1.0, 2.0, 3.0};

  (++SomeVector) *= 2;

  cout << "x = " << SomeVector.x
    << ", y = " << SomeVector.y
    << ", z = " << SomeVector.z;
}
x = 4, y = 6, z = 8

Test your Knowledge

Overloading the -= Operator

How could we implement the -= operator for Vector3 to enable expressions like this:

int main(){
  Vector3 A{1.0, 2.0, 3.0};
  Vector3 B{0.0, 0.0, 2.0};

  A -= B;
}

A should have a value of {1.0, 2.0, 1.0} after this operation.

Prefix and Postfix Operators

We have previously seen the increment operator ++ in action with built-in types like integers. Earlier in the course, we mentioned that the operator can go either before or after the operand.

These are two different operators, but there is a subtle difference in how they're expected to work. Let's go through an example with the built-in int type.

After running the following code, MyNumber will be 6:

int MyNumber { 5 };
MyNumber++;

Specifically, this is called the postfix increment operator, because the ++ appears after the operand. There is also the prefix increment operator:

int MyNumber { 5 };
++MyNumber;

After running the above code, MyNumber will also be 6, just like when we used the postfix operator.

The difference is the value that is returned from each operator. The postfix operator will return the value of the int before it is incremented. The prefix operator will return the value of the int after it is incremented.

This is relevant if we're immediately using the operator within an expression:

#include <iostream>
using namespace std;

int main(){
  int x{5};
  cout << "x++: " << x++;
  cout << " (x is now " << x << ")\n";

  int y{5};
  cout << "++y: " << ++y;
  cout << " (y is now " << y << ")\n";
}

Both operands eventually have the same value, but the postfix variant x++ returns the old value, whilst the prefix variant ++y returned the new value:

x++: 5 (x is now 6)
++y: 6 (y is now 6)

Test your Knowledge

Prefix and Postfix Operators

Consider the following code:

void MyFunction(int x){};

int main(){
  int x{1};
  MyFunction(x++);
  MyFunction(++x);
}

What argument is used in our calls to MyFunction()?

Overloading Postfix Operators

As we've seen above, we can overload the prefix operators in the same way we overload any other operator.

To overload a postfix operator, the syntax is a little inelegant. We need to specify an additional, unused int parameter in our function prototype. We typically don't give this extra parameter a name.

So, to overload the postfix ++ operator as a free function, it would look like this:

Vector3 operator++(Vector3 Vec, int){
  // ...
}

Using a member function, it looks like this:

struct Vector3 {
  Vector3 operator++(int){
    // ...
  }
};

The expected behavior of the postfix ++ operator is to increment the operand, but to return an object that had the original value, before the increment was applied.

To do this, we typically have three steps:

  1. Create a copy of the object with its current value
  2. Modify the object to increment it
  3. Return the original copy

It looks like this:

struct Vector3 {
  float x;
  float y;
  float z;

  Vector3 operator++(int){
    Vector3 temp{*this};
    ++x;
    ++y;
    ++z;
    return temp;
  }
};

Typically when we're implementing the postfix operator, we've also implemented the prefix operator.

So our postfix operator can make use of it, allowing our code to be simplified to something like this:

struct Vector3 {
  float x;
  float y;
  float z;

  // Prefix operator
  Vector3& operator++(){
    ++x;
    ++y;
    ++z;
    return *this;
  }

  // Postfix operator
  Vector3 operator++(int){
    Vector3 temp{*this};
    ++(*this);
    return temp;
  }
};

Test your Knowledge

Implementing the Postfix -- Operator

How can we add the postfix -- operator to the following class? Note that the prefix -- is already available:

struct Vector3 {
  float x;
  float y;
  float z;

  // Prefix -- operator
  Vector3& operator--(){
    --x;
    --y;
    --z;
    return *this;
  }

  // Postfix -- operator
  // ???
};

Summary

In this lesson, we explored the this pointer, a fundamental concept for understanding object-oriented programming in this language.

We began by explaining what the this pointer is - a special pointer that points to the object for which a member function is called. We then delved into various practical applications:

  1. Dereferencing this: We discussed how this can be dereferenced to access the object itself and how it can be used to access a pointer, a reference, or a copy of the object.
  2. Identifying the Caller: We examined a use case where this helps in identifying which object called a member function, allowing our objects to interact with each other in more complex ways.
  3. Chaining Function Calls: We showed how this can be used to return the current object, enabling the chaining of member function calls for more fluent and readable code.
  4. Overloading Operators: We also covered how this plays a crucial role in overloading operators like ++ and *= to ensure our custom types are consistent with built-in types.
Next Lesson
Lesson 33 of 60

Dangling Pointers and References

Learn about an important consideration when returning pointers and references from functions

Questions & Answers

Answers are generated by AI models and may not have been reviewed. Be mindful when running any code on your device.

Implementing the Singleton Pattern
How can I use the this pointer to implement the Singleton design pattern in C++?
Implementing the Composite Pattern
How can I use the this pointer to implement the composite design pattern?
Using this in Multithreaded Code
What are the best practices for using the this pointer in multi-threaded environments?
Implementing the Observer Pattern
How can I use the this pointer to implement the observer pattern in C++?
Using this in Static Functions
Is it possible to use the this pointer in static member functions? If not, why?
Or Ask your Own Question
Get an immediate answer to your specific question using our AI assistant