4b - Operator Overloading
4b - Operator Overloading
Programming
Mohammad Salman
Dean’s Fellow
Operator Overloading
An example before we continue on with this topic.
https://www.learncpp.com/cpp-tutorial/introduction-to-operator-overloading/
(SS) What will be the output of this program?
https://www.learncpp.com/cpp-tutorial/overloading-operators-using-member-functions/
class Fraction int main()
{ {
private: Fraction fraction1{1, 2}; // 1/2
int m_num;
int m_den;
Fraction fraction2{fraction1}; // 1/2
return res;
The reason we need a single explicit parameter is because the overload is a member function.
To call a member function, you need to call it with an object of that class.
• The first operand gets passed in as the implicit object, and
• The second operand gets passed in as the explicit parameter.
class Fraction
// In main()
{ fraction1 * fraction2
private:
int m_num; // Compiler’s interpretation
int m_den; fraction1.operator*(fraction2
)
public:
// Other stuff here omitted due to lack of space
Fraction operator*(const Fraction& fr2) const
{
int numRes = getNum() * fr2.getNum();
int denRes = getDen() * fr2.getDen();
return res;
}
};
Note // In main()
This first const is only fraction1 * fraction2
there because the
member function has // Compiler’s interpretation
been marked const. fraction1.operator*(fraction2
)
return res;
}
return res;
}
return res;
}
When overloading an operator using a
member function…
• The left operand becomes the implicit (*this) object.
public:
Fraction(int num, int den)
: m_num{num}, m_den{den}
{}
return out;
}
If you wanted this to be defined as a member function, the interpretation would be as follows.
std::cout <<
point1;
(std::cout).operator<<(point1)
;
So operator<<() shouldn’t be a member of class Point.
Now, we would need to somehow modify the class ostream (to which cout belongs to).
Since ostream is built into the language, we cannot modify it.
Therefore, overloads for operator<<() and for operator>>() (not an exhaustive list) have to be
defined as normal (or non-member) functions.
When overloading an operator using a
member function…
• The left operand becomes the implicit *this object.
• Assignment ( = )
• Subscript ( [] )
• Function call ( () )
• Member selection ( -> )
• And some others.
https://www.learncpp.com/cpp-tutorial/overloading-the-assignment-operator/
The Copy Assignment Operator
operator=
• It is used to copy values from one object to another already
existing object.
int x = 5;
int y = 6;
int z = 7;
x = y = z;
std::cout << x << ' ' << y << ' ' <<
z;
Copy Constructor vs. operator=
• The purpose of the copy constructor and the copy assignment
operator are almost equivalent -- both copy one object to
another.
class Fraction
// return the implicit object so we can chain this
{ operator
private: return *this;
int m_num; }
int m_den;
};
Fraction& operator= (const Fraction& fraction)
{
// in main() // self-assignment guard
f1 = f1; if (this == &fraction)
// OR return *this;
f1.operator=(f1)
; m_num = fraction.m_num; // can handle self-assignment
class Fraction properly
{ m_den = fraction.m_den; // can handle self-assignment
private: properly
int m_num;
int m_den; return *this;
} Note
public:
Fraction(int num, int den) In fact this is an even better
: m_num{ num }, m_den{ den } implementation.
{} In this example, the self-
assignment guard isn’t doing
Fraction(const Fraction& fr) much. However, there are cases
: m_num{ fr.m_num }, m_den{ fr.m_den }
{}
where it becomes crucial.
};
We would need to provide the overload
class Fraction for operator= (as a class member) for int main()
{ this to work. {
private: Fraction fiveThirds{ 5,
int m_num; fiveThirds.operator=(4) 3 };
int m_den;
public: fiveThirds = 4;
Fraction(int num, int den)
: m_num{ num }, m_den{ den } return 0;
{} }
Fraction(const Fraction& fr)
: m_num{ fr.m_num }, m_den{ fr.m_den }
{}
There is a nuance here that could make this work without providing the overload for operator=,
but we will have to digress from the main topic. For the interested folks, the nuance relates to
implicit type conversion of the integer into the object itself.
Self Study
What will be the output here?
int main()
class Fraction {
{ Fraction f1 { 5, 3 };
private: Fraction f2 { 7, 2 };
int m_num; Fraction f3 { 9, 5 };
int m_den;
f1 = f2 = f3; // chained
public: assignment
Fraction(int num, int den)
: m_num{ num }, m_den{ den }
{} return 0;
}
Fraction(const Fraction& fr)
: m_num{ fr.m_num }, m_den{ fr.m_den }
{}
https://www.learncpp.com/cpp-tutorial/shallow-vs-deep-copying/
class MyList The Rule of Three guideline: If you find yourself defining
{ either the copy ctor, or the dtor, or that third thing, it’s
private: recommended to define the other two as well.
double* m_array;
int m_length;
But what is that third thing that we need to be mindful about?
public:
MyList(int length) int main()
: m_array{ new double[length] }, {
m_length{ length } MyList list1{ 5 };
{}
{
MyList(const MyList& ob)
: m_array{ new
MyList
double[ob.m_length] }, list2{ list1 };
m_length{ ob.m_length } }
{
for(int i{0}; i < ob.m_length; ++i) return 0;
{ }
m_array[i] = ob.m_array[i];
}
}
~MyList()
{
delete[] m_array;
}
class MyList Will the copy ctor be called here?
{ No!
private: The two objects have already been created, I’m just
double* m_array;
copying over the attributes of list2 into list1.
int m_length;
public:
MyList(int length) int main()
: m_array{ new double[length] }, {
m_length{ length } MyList list1{ 5 };
{} MyList list2{ 3 };
MyList(const MyList& ob)
: m_array{ new
list2 = list1;
double[ob.m_length] },
m_length{ ob.m_length } return 0;
{ }
for(int i{0}; i < ob.m_length; ++i)
{
m_array[i] = ob.m_array[i];
The following slide shows how list1 and list2
}
}
will look like before the assignment operation.
~MyList()
{
delete[] m_array;
}
Stack Memory Heap Memory
Automatic Memory Allocation Dynamic Memory Allocation
public:
MyList(int length) int main()
: m_array{ new double[length] }, {
m_length{ length } MyList list1{ 5 };
{} MyList list2{ 3 };
MyList(const MyList& ob)
: m_array{ new
list2 = list1;
double[ob.m_length] },
m_length{ ob.m_length } return 0;
{ }
for(int i{0}; i < ob.m_length; ++i)
{
m_array[i] = ob.m_array[i];
What this does, is shown on the next slide.
}
}
~MyList()
{
delete[] m_array;
}
Stack Memory Heap Memory
Automatic Memory Allocation Dynamic Memory Allocation
~MyList()
{
delete[] m_array;
}
};
list2 = list1;
This is our state before assignment. Now, let’s try to figure out the
implementation for operator=. // Compiler's interpretation
list2.operator=(list1);
// do the copy
for (int i{ 0 }; i < m_length; ++i)
{
m_array[i] = source.m_array[i];
}
}
else
{
// this->m_array is already nullptr
}
// return by reference
return *this;
}
list2 = list1;
// Compiler's interpretation
list2.operator=(list1);
// do the copy
for (int i{ 0 }; i < m_length; ++i)
{
m_array[i] = source.m_array[i];
}
}
else
{
// this->m_array is already nullptr
}
// return by reference
return *this;
}
list2 = list1;
// Compiler's interpretation
list2.operator=(list1);
// do the copy
for (int i{ 0 }; i < m_length; ++i)
{
m_array[i] = source.m_array[i];
}
}
else
{ • Note that when doing delete[] m_array at
// this->m_array is already nullptr
}
the very start of this function, m_array could
be a nullptr.
// return by reference • Note that it is safe to delete a nullptr;
return *this; nothing will happen.
}
list2 = list1;
MyList& MyList::operator=(const MyList& source)
{
// Compiler's interpretation
if (this == &fraction)
list2.operator=(list1);
return *this;
• std::vector
• std::string
• Move semantics and Rule of Five are not part of this course.
(SS) In our own implementation of operator=, assume the check for self-guard didn’t exist, and you did this:
list1 = list1;
Then, within the conditional, you will not reallocate the array since
source.m_array (which is also m_array in the case of self-
assignment), is nullptr. So a self-assignment causes the array
associated with list1 to be freed (which doesn’t make sense).
The array you just allocated is not initialized (or could be initialized to
zero). In either case, you have lost your original information, which
again, doesn’t make sense.