this
Pointerthis
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
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:
Pointer
function returns a pointer to the current object, using this
and a return type of SomeType*
Reference
function returns a reference to the current object, using *this
and a return type of SomeType&
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.
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!
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
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
-=
OperatorHow 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.
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
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)
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()
?
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:
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;
}
};
--
OperatorHow 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
// ???
};
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++
.
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:
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.this
helps in identifying which object called a member function, allowing our objects to interact with each other in more complex ways.this
can be used to return the current object, enabling the chaining of member function calls for more fluent and readable code.this
plays a crucial role in overloading operators like ++
and *=
to ensure our custom types are consistent with built-in types.In the next lesson, we delve into pointers and references to local variables. Key focus areas will include:
This lesson will equip you with the knowledge to write bug-free code, especially when dealing with functions and their return values.
this
PointerLearn about the this
pointer in C++ programming, focusing on its application in identifying callers, chaining functions, and overloading operators.
Become a software engineer with C++. Starting from the basics, we guide you step by step along the way