The this
Pointer
Learn about the this
pointer in C++ programming, focusing on its application in identifying callers, chaining functions, and overloading operators.
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, usingthis
and a return type ofSomeType*
- the
Reference
function returns a reference to the current object, using*this
and a return type ofSomeType&
- the
Copy
function returns a copy of the current object, using*this
and a return type ofSomeType
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:
- Create a copy of the object with its current value
- Modify the object to increment it
- 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:
- Dereferencing
this
: We discussed howthis
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. - 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. - 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. - 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.
Dangling Pointers and References
Learn about an important consideration when returning pointers and references from functions