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;
}