The this Pointer

Learn about the this pointer in C++ programming, focusing on its application in identifying callers, chaining functions, and overloading operators.
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

Free, Unlimited Access
3D art showing a fantasy pirate character
Ryan McCombe
Ryan McCombe
Edited

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 them, so they 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.

Using Free Functions

Operators like ++ and *= can also be implemented using free functions. To get the equivalent behavior as we did using the this pointer, they need to receive the operand by reference, and return that same reference:

#include <iostream>
using namespace std;

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

Vector3& operator++(Vector3& vec){ 
  ++vec.x;
  ++vec.y;
  ++vec.z;
  return vec; 
}

Vector3& operator*=(Vector3& vec, int mult){ 
  vec.x *= mult;
  vec.y *= mult;
  vec.z *= mult;
  return vec; 
}

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

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++ returned 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 override 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
  // ???
};

Postfix vs Prefix Operator Performance

As the previous example perhaps demonstrates, the postfix increment and decrement operators need to do slightly more work than their prefix counterparts.

Copying our object to support the expected postfix behavior has a performance overhead. Therefore, ++SomeObject is generally faster than SomeObject++

Because of this, when we’re calling these operators, if we don’t care about the return value of the expression, we should default to using the prefix variations.

In other words, prefer ++SomeObject unless we have a specific reason to use SomeObject++.

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.

Preview: Returning References and Pointers from Functions

In the next lesson, we delve into pointers and references to local variables. Key focus areas will include:

  • Understanding the lifespan of local variables and the risks associated with returning pointers or references to them.
  • Exploring scenarios where returning a pointer or reference to a local variable can lead to undefined behavior.
  • Learning best practices to avoid common pitfalls when dealing with pointers and references to local variables.

This lesson will equip you with the knowledge to write bug-free code, especially when dealing with functions and their return values.

Was this lesson useful?

Edit History

  • Refreshed Content

  • First Published

Ryan McCombe
Ryan McCombe
Edited
3D art showing a progammer setting up a development environment
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

Free, Unlimited Access
References and Pointers
3D art showing a progammer setting up a development environment
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

Free, unlimited access

This course includes:

  • 56 Lessons
  • Over 200 Quiz Questions
  • Capstone Project
  • Regularly Updated
  • Help and FAQ
Next Lesson

Returning References and Pointers from Functions

Learn about an important consideration when returning pointers and references from functions
3D art showing a female blacksmith character
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved