0% found this document useful (0 votes)
4 views31 pages

3 - This Pointer

Uploaded by

i.a.m
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
4 views31 pages

3 - This Pointer

Uploaded by

i.a.m
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
You are on page 1/ 31

Object-Oriented

Programming
Mohammad Salman
Dean’s Fellow
The this Pointer
It’s hidden from you (kind of). You can use it explicitly if you want
to though.

https://www.learncpp.com/cpp-tutorial/the-hidden-this-pointer-and-member-function-chaining/
How does the compiler interprets
member function calls?
• We have already seen this before actually.

• Let’s take the Complex numbers example from your lab.


How does the compiler interprets
member function calls?
• Recall that you had a member function (or method) call like so.
• c1 and c2 were two Complex type objects in your program.
c1.add(c2);

• Compiler interprets the above as follows.


Complex::add(&c1, c2);

• c1 is now being passed (implicitly) by address to the function.


How does the compiler interprets
member function calls?
• Note, however, that your Complex Complex::add(Complex c2)
{
member function looks like Complex c3;
as follows. c3.m_real = m_real +
c2.m_real;
c3.m_imag = m_imag +
• It only has a single c2.m_imag;
argument.
return c3;
}
// Compiler’s interpretation
• Whereas the compiler is add(&c1, c2);
now passing two
arguments.
How does the compiler interprets
member function calls?
• The compiler also updates your member function as shown.

c1.add(c2);

Complex Complex::add(Complex* const this, Complex Compiler’s interpretation


c2) add(&c1, c2);
{
Complex c3;

c3.m_real = this->m_real + c2.m_real;

c3.m_imag = this->m_imag + c2.m_imag;

return c3;
}
Pointer to an int Pointer to a pointer to an int
int* ptr int** ptr
Read from right to left Read from right to left
Note
You can replace int
with any built-in data type
and class types.
const pointer to an int
int* const ptr
const pointer to a const int
Read from right to left
const int* const ptr
Pointer to a const Read from right to left

const intint* ptr


Read from right to left
Two dimensions
• Can the pointer be

What do these pointers mean? changed?


• Can the pointed
data be changed?

• int* const ptr (const pointer to an int)


• The value of the pointer cannot be changed (cannot point at anything else).
• However the entity to which it is pointing at can be changed.

• const int* ptr (pointer to a const int)


• The value of the pointer itself can be changed (can be made to point at
something else).
• The value of the entity to which the pointer is pointing at, could not be
changed.

• const int* const ptr (const pointer to a const int)


• Neither the entity nor the pointer can be changed.
Complex Complex::add(Complex& c2) c1.add(c2);
{
Complex c3; Compiler’s interpretation
add(&c1, c2);
// c3.m_real = this->m_real +
c2.m_real; Complex* const
// c3.m_imag = this->m_imag + this
c2.m_imag; &c1

this = nullptr;

this = &c2;
Complex c1 Complex c2
return c3; double double double double
} m_real m_imag m_real m_imag
Complex c3 // Methods // Methods
double double
m_real m_imag Both of these objects were created in
main().
// Methods c3 would be created, and be local to
add().
Complex Complex::add(Complex& c2) c1.add(c2);
{
Complex c3; Compiler’s interpretation
add(&c1, c2);
// c3.m_real = this->m_real +
c2.m_real; Neither of these is allowed because
// c3.m_imag = this->m_imag + this is a const pointer to object;
c2.m_imag; its value cannot be changed.

this = nullptr;

this = &c2;

return c3;
}

Note
We haven’t talked about lvalues and rvalues in this
course.
In simple terms, you can think of lvalue as a
modifiable variable.
Pointer to an int Pointer to a pointer to an int
int* ptr int** ptr
Read from right to left Read from right to left

This one is the this pointer.

const pointer to an int


int* const ptr
const pointer to a const int
Read from right to left
const int* const ptr
Pointer to a const Read from right to left

const intint* ptr


Read from right to left
The hidden “this” pointer
• Note that this this pointer is a const pointer.
Complex Complex::add(Complex* const this, Complex c2)

• This means that you cannot change this’s value.


• It can only point to the implicit object.
• You cannot change its value (not even to nullptr).
• You can change the attributes of the entity to which it is pointing at
(which is the implicit object).
The hidden “this” pointer
• this pointer will always be the leftmost parameter.
Complex Complex::add(Complex* const this, Complex c2)
In learncpp, they’ve created class Simple, and
instantiated an object of this class, Simple simple.

Note
The members we have seen thus far are non-static
members. We haven’t talked about static members yet.
#include<iostream>

class A
{
private:
int m_num1;
Self Study
public: Is this program valid?
A() : m_num1{ 0 } {}

int getNum1() const


{
this->m_num1 = 5;
return this->m_num1; // same as "return
m_num1;"
}

};

int main()
{
A ob;
ob.getNum1();

return 0;
}
Some use-cases of this
First up, is Method Chaining
The following will work.

What will be the output? Can I do this chaining


8 right now btw?
This is somewhat of a hassle; three lines.
How could we do something like so?
calc.add(5).sub(3).mult(4);

class Calc int main()


{ {
private: Calc calc{};
int m_value; calc.add(5); // returns void
calc.sub(3); // returns void
public: calc.mult(4); // returns void
Calc() : m_value{ 0 } {}
std::cout << calc.getValue() << '\n';
void add(int value) { m_value += value; }
void sub(int value) { m_value -= value; } return 0;
void mult(int value) { m_value *= }
value; }

int getValue() const { return m_value; }


};
We can’t have the functions as void, otherwise chaining won’t work.
Something has to be returned.

We can return by reference.

class Calc
{
private:
int m_value{};

public:
Calc() : m_value{ 0 } {}

Calc& add(int value) { m_value += value; return *this; }


Calc& sub(int value) { m_value -= value; return *this; }
Calc& mult(int value) { m_value *= value; return
*this; }

int getValue() const { return m_value; }


};
We can’t have the functions as void, otherwise chaining won’t work.
Something has to be returned.

We can return by reference.

class Calc
{
private:
int m_value{};

public:
Calc() : m_value{ 0 } {}

Calc& add(int value) { m_value += value; return *this; }


Calc& sub(int value) { m_value -= value; return *this; }
Calc& mult(int value) { m_value *= value; return
*this; }

int getValue() const { return m_value; }


};
You go to Calc::add(). The value of the calc object changes from 0 to 5.
What gets returned?

The object, calc, itself.


calc.add(5).sub(3).mult(4);

class Calc
{
private:
int m_value{};

public:
Calc() : m_value{ 0 } {}

Calc& add(int value) { m_value += value; return *this; }


Calc& sub(int value) { m_value -= value; return *this; }
Calc& mult(int value) { m_value *= value; return
*this; }

int getValue() const { return m_value; }


};
You go to Calc::add(). The value of the calc object changes from 0 to 5.
What gets returned?

The object, calc, itself.


calc.add(5).sub(3).mult(4);
calc

class Calc
{
private:
int m_value{};

public:
Calc() : m_value{ 0 } {}

Calc& add(int value) { m_value += value; return *this; }


Calc& sub(int value) { m_value -= value; return *this; }
Calc& mult(int value) { m_value *= value; return
*this; }

int getValue() const { return m_value; }


};
Because calc was returned, now we can continue our evaluation.

3 will be subtracted from the current value (which is 5) to give 2.


calc will be returned again.
calc.sub(3).mult(4);

class Calc
{
private:
int m_value{};

public:
Calc() : m_value{ 0 } {}

Calc& add(int value) { m_value += value; return *this; }


Calc& sub(int value) { m_value -= value; return *this; }
Calc& mult(int value) { m_value *= value; return
*this; }

int getValue() const { return m_value; }


};
Finally 4 gets multiplied to the current value (2) to give 8.

calc.mult(4);

class Calc
{
private:
int m_value{};

public:
Calc() : m_value{ 0 } {}

Calc& add(int value) { m_value += value; return *this; }


Calc& sub(int value) { m_value -= value; return *this; }
Calc& mult(int value) { m_value *= value; return
*this; }

int getValue() const { return m_value; }


};
calc is returned again, but nothing happens since there are no further operations left.

calc;

class Calc
{
private:
int m_value{};

public:
Calc() : m_value{ 0 } {}

Calc& add(int value) { m_value += value; return *this; }


Calc& sub(int value) { m_value -= value; return *this; }
Calc& mult(int value) { m_value *= value; return
*this; }

int getValue() const { return m_value; }


};
When you do calc.getValue(), you see the updated value of calc’s attribute.

We will see these concepts of method chaining and return by reference again in operator overloading.

int main()
{
Calc calc{};

class Calc // method chaining


{ calc.add(5).sub(3).mult(4);
private:
int m_value{}; std::cout << calc.getValue() << '\
n';
public:
Calc() : m_value{ 0 } {} return 0;
}
Calc& add(int value) { m_value += value; return *this; }
Calc& sub(int value) { m_value -= value; return *this; }
Calc& mult(int value) { m_value *= value; return
*this; }

int getValue() const { return m_value; }


};
What happens if your methods return by address?

int main()
{
Calc calc{};
calc.add(5)->sub(3)->mult(4); // method
chaining
class Calc
{
std::cout << calc.getValue() << '\n';
private:
int m_value{};
return 0;
}
public:
Calc() : m_value{ 0 } {}

Calc* add(int value) { m_value += value; return this; }


Calc* sub(int value) { m_value -= value; return this; }
Calc* mult(int value) { m_value *= value; return this; }

int getValue() const { return m_value; }


};
What happens if your methods return by address?

int main()
{
Calc calc{};
calc.add(5)->sub(3)->mult(4); // method
chaining
class Calc
{
std::cout << calc.getValue() << '\n';
private:
int m_value{};
return 0;
}
public:
Calc() : m_value{ 0 } {}

Calc* add(int value) { m_value += value; return this; }


Calc* sub(int value) { m_value -= value; return this; }
Calc* mult(int value) { m_value *= value; return this; }

int getValue() const { return m_value; }


};
What happens if your methods return by address?

int main()
{
Calc calc{};
calc.add(5)->sub(3)->mult(4); // method
chaining
class Calc
{
std::cout << calc.getValue() << '\n';
private:
int m_value{};
return 0;
}
public:
Calc() : m_value{ 0 } {}

Calc* add(int value) { m_value += value; return this; }


Calc* sub(int value) { m_value -= value; return this; }
Calc* mult(int value) { m_value *= value; return this; }

int getValue() const { return m_value; }


};
What happens if your methods return by value?

What will be the output?


5

int main()
{
Calc calc{};
calc.add(5).sub(3).mult(4); // method chaining
class Calc
std::cout << calc.getValue() << '\n';
{
private:
return 0;
int m_value{};
}
public:
Calc() : m_value{ 0 } {}

Calc add(int value) { m_value += value; return *this; }


Calc sub(int value) { m_value -= value; return *this; }
Calc mult(int value) { m_value *= value; return *this; }

int getValue() const { return m_value; }


};
Another use-case is to guard against self-
referencing
• Read the following slides as self-study.
class A Self Study int main()
{ {
private: Guarding against self- A ob1;
int m_num1; referencing. Good idea A ob2;

public: to probably draw the A* ptr1{ &ob1 };


A() : m_num1{ -1 } {} things that are
std::cout << "Print 1\n";
void printAddress(A* addr)
happening in main(). ptr1->printAddress(ptr1);
{
if(addr == this) A* ptr2{ &ob1 };
{ std::cout << "Print 2\n";
std::cout << "Value of \"this\" = " ptr2->printAddress(ptr1);
<< addr << std::endl;
} std::cout << "Print 3\n";
else ob1.printAddress(&ob1);
{
std::cout << "Some other object's address = std::cout << "Print 4\n";
" ob1.printAddress(&ob2);
<< addr << std::endl;
} std::cout << "Print 5\n";
} ob2.printAddress(&ob2);
};
return 0;
}

You might also like